Compare commits

...

53 Commits

Author SHA1 Message Date
Alex Orlenko 84811b1be3 v0.8.10 2023-08-16 00:20:05 +01:00
Alex Orlenko 032d4af896 Fix loading luau code starting with \t 2023-08-16 00:15:34 +01:00
Alex Orlenko c9099a4364 Update to Luau 0.590 2023-08-16 00:15:05 +01:00
Alex Orlenko e4eeee05c4 Pin (more strict) lua-src and luajit-src versions 2023-08-16 00:09:20 +01:00
Alex Orlenko 15e353a7f8 v0.8.9 2023-05-16 23:02:19 +01:00
Alex Orlenko 765117c2bb Update tarpaulin settings 2023-05-16 23:02:17 +01:00
Alex Orlenko 573d71345f Don't set html_root_url (it's not recommended) 2023-05-16 22:55:29 +01:00
Alex Orlenko 72de17bf47 Allow deserializing Lua null into unit(()) or unit struct. See #264 2023-05-16 22:53:37 +01:00
Alex Orlenko 5a96e80266 Use lua_closethread instead of lua_resetthread in vendored mode (introduced in Lua 5.4.6) 2023-05-16 22:50:46 +01:00
Alex Orlenko bfdb4087b8 Update minimal (vendored) Lua 5.4 to 5.4.6 2023-05-16 22:49:49 +01:00
Alex Orlenko eb84284824 Fix ref_stack_exhaustion test (Lua 5.4.6) 2023-05-16 22:12:36 +01:00
Alex Orlenko 34679e105d v0.8.8 2023-03-05 17:50:53 +00:00
Alex Orlenko bc194981fc Optimize userdata methods call when __index and fields_getters are nil 2023-03-05 14:43:12 +00:00
Alex Orlenko c9715aa5d9 Fix potential deadlock when trying to reuse dropped RegistryKey.
If no free registry id found, we call protect_lua! macro while keeping mutex guard to the unref list.
Protected calls can trigger garbage collection and if RegistryKey is placed in userdata being collected, this can lead to deadlock.
The solution is drop mutex guard as soon as possible.
Also this commit includes optimization in creating reference in Lua registry.
2023-03-05 14:39:22 +00:00
Alex Orlenko c108dc8213 Force protected mode for long enough strings 2023-03-05 14:35:15 +00:00
Alex Orlenko e86ef9d755 v0.8.7 2023-01-04 16:15:23 +00:00
Alex Orlenko 05eb20f9c6 Fix subtraction overflow when calculating index for MultiValue::get().
Return `None` instead of panic in debug mode.
Fixes #232
2022-12-15 21:10:31 +00:00
Alex Orlenko 9716918517 Fix lifetime of DebugNames in Debug::names() and DebugSource in Debug::source().
This can cause use-after-free if used wrong.
Now invalid code would not compile.
Reported in #230
2022-12-09 23:55:38 +00:00
Alex Orlenko c88417a3b6 Redefine _VERSION for Luau to include version number.
https://github.com/khvzak/mlua/discussions/228
2022-12-03 21:21:28 +00:00
Alex Orlenko 6e95386f30 Update Luau to 0.555 (luau-src 0.5.0)
This version includes inreased LUAI_MAXCSTACK limit (100000)
2022-12-03 17:55:26 +00:00
Alex Orlenko 6807dfa22e v0.8.6 2022-11-07 01:05:14 +00:00
Alex Orlenko f27c49f931 Fix bug when recycled Registry slot can be set to Nil.
This can result in allocating the same slot twice and rewriting old value.
Lua uses (registry) table length to find next free slot and having Nil in the middle of the table can impact length calculation.
With this fix we ensure that Nil values uses a special LUA_REFNIL slot.
2022-11-07 00:10:57 +00:00
Alex Orlenko 693a808b6e v0.8.5 2022-10-30 12:31:26 +00:00
Alex Orlenko a6ca65aa74 Better checks and tests when trying to modify a Luau readonly table 2022-10-30 11:41:09 +00:00
Alex Orlenko a7278cab78 Fix Table::raw_push for luau when readonly 2022-10-29 23:02:19 +01:00
Alex Orlenko e1bbd00a33 Fix getting caller information from Lua::load 2022-10-28 10:43:05 +01:00
Alex Orlenko e98998d6ac Add push/pop methods to Table 2022-10-23 23:27:17 +01:00
Alex Orlenko cca177df5b Minor serde optimizations 2022-10-23 03:13:24 +01:00
Alex Orlenko 65396a910f Optimize Lua::create_table to use reference thread if possible 2022-10-23 00:24:04 +01:00
Alex Orlenko de69d10d73 Optimize Lua::create_string to use reference thread if possible 2022-10-23 00:14:13 +01:00
Alex Orlenko 959e61b97c Optimize tables:
- Use reference thread directly for simple cases
- Fix issue when calling raw_set on readonly table (Luau)
- Add fasttrack methods for get/set/len when metatable is not set
2022-10-22 23:45:58 +01:00
Alex Orlenko 1040c0a347 Fix typo 2022-10-22 23:44:21 +01:00
Alex Orlenko 9045419586 Strings optimizations: get access to bytes directly from ref thread. 2022-10-22 23:43:08 +01:00
Alex Orlenko fcd162f3eb Replace Lua::ref_thread_exec 2022-10-22 23:39:49 +01:00
Alex Orlenko 0354703dbf Update formatting (Lua registry values) 2022-10-22 23:34:19 +01:00
Alex Orlenko 9831d0e397 Check that allocation does not exceed isize::MAX
See https://github.com/rust-lang/rust/issues/101899
2022-10-22 23:13:02 +01:00
Alex Orlenko 7f14d93c2b v0.8.4 2022-10-09 14:40:15 +01:00
Alex Orlenko 29c6c9cb58 Fix clippy warnings 2022-10-09 14:08:33 +01:00
Alex Orlenko e01af22bac Update push_userdata_ref 2022-10-09 13:56:30 +01:00
Alex Orlenko cfa959e599 Don't allocate extra byte for userdata (luau) 2022-10-09 13:56:06 +01:00
Alex Orlenko e523fb2c86 Update to Luau 0.548 (luau-src >=0.4) 2022-10-08 23:34:47 +01:00
Alex Orlenko 81a9998559 Update CI 2022-09-13 00:09:03 +01:00
Alex Orlenko 050ac4f5ad Add luaL_setfuncs to preserved functions 2022-09-05 23:13:06 +01:00
Alex Orlenko 967cbb53b0 Update compile tests 2022-08-23 23:12:37 +01:00
Alex Orlenko 44a8d86d8a Fix error message in tests (luau) 2022-08-23 23:08:00 +01:00
Alex Orlenko 33278d4a64 Check if chunk is empty in luaL_loadbufferx for Luau (fixes #200) 2022-08-23 11:35:08 +01:00
Trisha 20a16839aa Fix typo in doc comment 2022-08-23 01:47:17 +01:00
Alex Orlenko 83ed442bf9 v0.8.3 2022-08-02 12:43:15 +01:00
Alex Orlenko bf6708ba58 Fallback to Lua internal allocator if unable to create Lua VM with Rust one.
This should fix #194
2022-08-02 10:35:39 +01:00
Alex Orlenko 0cd724f63b Fix Lua assertion when inspecting another thread stack.
The thread can be dead and it's not safe to call __tostring metamethod (if present) on error object.
Fixes #195
2022-08-01 22:07:39 +01:00
Alex Orlenko 5330b900fd Check that Lua state is non-null in init_from_ptr() 2022-08-01 15:21:14 +01:00
Alex Orlenko ee32dc33f3 Check that lua_newstate() returns non-null Lua state 2022-08-01 12:56:36 +01:00
Alex Orlenko ab029b087d Close to-be-closed variables for Lua 5.4 when using call_async functions
Fixes #192
2022-07-31 15:33:02 +01:00
39 changed files with 926 additions and 419 deletions
+5 -5
View File
@@ -10,14 +10,14 @@ jobs:
options: --security-opt seccomp=unconfined options: --security-opt seccomp=unconfined
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Generate code coverage - name: Generate coverage report
run: | run: |
cargo tarpaulin --verbose --features lua54,vendored,async,send,serialize,macros --out xml --exclude-files benches --exclude-files build --exclude-files mlua_derive --exclude-files src/ffi --exclude-files tests cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files src/ffi/*/*
- name: Upload to codecov.io - name: Upload report to codecov.io
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v3
with: with:
token: ${{secrets.CODECOV_TOKEN}} token: ${{secrets.CODECOV_TOKEN}}
fail_ci_if_error: false fail_ci_if_error: false
+134 -143
View File
@@ -7,35 +7,34 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, macos-latest, windows-latest] os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable] rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau] lua: [lua54, lua53, lua52, lua51, luajit, luau]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1 - name: Build ${{ matrix.lua }} vendored
- name: Build ${{ matrix.lua }} vendored run: |
run: | cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored" cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" shell: bash
shell: bash - name: Build ${{ matrix.lua }} pkg-config
- name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-22.04' }}
if: ${{ matrix.os == 'ubuntu-20.04' && matrix.lua != 'lua54' }} run: |
run: | sudo apt-get update
sudo apt-get update sudo apt-get install -y --no-install-recommends liblua5.4-dev liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
sudo apt-get install -y --no-install-recommends liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev cargo build --features "${{ matrix.lua }}"
cargo build --features "${{ matrix.lua }}"
build_aarch64_cross_macos: build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin name: Cross-compile to aarch64-apple-darwin
@@ -45,60 +44,57 @@ jobs:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua54, lua53, lua52, lua51, luajit]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
target: aarch64-apple-darwin target: aarch64-apple-darwin
override: true - name: Cross-compile
- name: Cross-compile run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
build_aarch64_cross_ubuntu: build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
needs: build needs: build
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua54, lua53, lua52, lua51, luajit]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
target: aarch64-unknown-linux-gnu target: aarch64-unknown-linux-gnu
override: true - name: Install ARM compiler toolchain
- name: Install ARM compiler toolchain run: |
run: | sudo apt-get update
sudo apt-get update sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross shell: bash
shell: bash - name: Cross-compile
- name: Cross-compile run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" shell: bash
shell: bash
build_armv7_cross_ubuntu: build_armv7_cross_ubuntu:
name: Cross-compile to armv7-unknown-linux-gnueabihf name: Cross-compile to armv7-unknown-linux-gnueabihf
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
needs: build needs: build
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51] lua: [lua54, lua53, lua52, lua51]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
target: armv7-unknown-linux-gnueabihf target: armv7-unknown-linux-gnueabihf
override: true - name: Install ARM compiler toolchain
- name: Install ARM compiler toolchain run: |
run: | sudo apt-get update
sudo apt-get update sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross shell: bash
shell: bash - name: Cross-compile
- name: Cross-compile run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" shell: bash
shell: bash
test: test:
name: Test name: Test
@@ -106,35 +102,34 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, macos-latest, windows-latest] os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable, nightly] rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau] lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v1 - name: Run ${{ matrix.lua }} tests
- name: Run ${{ matrix.lua }} tests run: |
run: | cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored" cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" shell: bash
shell: bash - name: Run compile tests (macos lua54)
- name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} run: |
run: | TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" -- --ignored shell: bash
shell: bash
test_with_sanitizer: test_with_sanitizer:
name: Test with address sanitizer name: Test with address sanitizer
@@ -142,25 +137,24 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04] os: [ubuntu-22.04]
rust: [nightly] rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau] lua: [lua54, lua53, lua52, lua51, luajit, luau]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v1 - name: Run ${{ matrix.lua }} tests with address sanitizer
- name: Run ${{ matrix.lua }} tests with address sanitizer run: |
run: | RUSTFLAGS="-Z sanitizer=address" \
RUSTFLAGS="-Z sanitizer=address" \ cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions shell: bash
shell: bash
test_modules: test_modules:
name: Test modules name: Test modules
@@ -168,27 +162,26 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, macos-latest] os: [ubuntu-22.04, macos-latest]
rust: [stable] rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua54, lua53, lua52, lua51, luajit]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v1 - name: Run ${{ matrix.lua }} module tests
- name: Run ${{ matrix.lua }} module tests run: |
run: | (cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module && cargo build --release --features "${{ matrix.lua }}") (cd tests/module/loader && cargo test --release --features "${{ matrix.lua }},vendored")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }},vendored") shell: bash
shell: bash
test_modules_windows: test_modules_windows:
name: Test modules on Windows name: Test modules on Windows
@@ -201,41 +194,39 @@ jobs:
run: run:
shell: msys2 {0} shell: msys2 {0}
steps: steps:
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install Rust & Lua - name: Install Rust & Lua
run: | run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
- name: Run ${{ matrix.lua }} module tests - name: Run ${{ matrix.lua }} module tests
run: | run: |
(cd tests/module && cargo build --release --features "${{ matrix.lua }}") (cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }}") (cd tests/module/loader && cargo test --release --features "${{ matrix.lua }}")
rustfmt: rustfmt:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
components: rustfmt components: rustfmt
override: true - run: cargo fmt -- --check
- run: cargo fmt -- --check
clippy: clippy:
name: Clippy check name: Clippy check
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau] lua: [lua54, lua53, lua52, lua51, luajit, luau]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: nightly toolchain: nightly
components: clippy components: clippy
override: true
- uses: actions-rs/clippy-check@v1 - uses: actions-rs/clippy-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
+47
View File
@@ -1,3 +1,50 @@
## v0.8.10
- Update to Luau 0.590 (luau0-src to 0.7.x)
- Fix loading luau code starting with \t
- Pin lua-src and luajit-src versions
## v0.8.9
- Update minimal (vendored) Lua 5.4 to 5.4.6
- Use `lua_closethread` instead of `lua_resetthread` in vendored mode (Lua 5.4.6)
- Allow deserializing Lua null into unit (`()`) or unit struct.
## v0.8.8
- Fix potential deadlock when trying to reuse dropped registry keys.
- Optimize userdata methods call when __index and fields_getters are nil
## v0.8.7
- Minimum Luau updated to 0.555 (`LUAI_MAXCSTACK` limit increased to 100000)
- `_VERSION` in Luau now includes version number
- Fixed lifetime of `DebugNames` in `Debug::names()` and `DebugSource` in `Debug::source()`
- Fixed subtraction overflow when calculating index for `MultiValue::get()`
## v0.8.6
- Fixed bug when recycled Registry slot can be set to Nil
## v0.8.5
- Fixed potential unsoundness when using `Layout::from_size_align_unchecked` and Rust 1.65+
- Performance optimizations around string and table creation in standalone mode
- Added fast track path to Table `get`/`set`/`len` methods without metatable
- Added new methods `push`/`pop`/`raw_push`/`raw_pop` to Table
- Fix getting caller information from `Lua::load`
- Better checks and tests when trying to modify a Luau readonly table
## v0.8.4
- Minimal Luau updated to 0.548
## v0.8.3
- Close to-be-closed variables for Lua 5.4 when using call_async functions (#192)
- Fixed Lua assertion when inspecting another thread stack. (#195)
- Use more reliable way to create LuaJIT VM (which can fail if use Rust allocator on non-x86 platforms)
## v0.8.2 ## v0.8.2
- Performance optimizations in handling UserData - Performance optimizations in handling UserData
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mlua" name = "mlua"
version = "0.8.2" # remember to update html_root_url and mlua_derive version = "0.8.10" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2021" edition = "2021"
repository = "https://github.com/khvzak/mlua" repository = "https://github.com/khvzak/mlua"
@@ -56,9 +56,9 @@ parking_lot = { version = "0.12", optional = true }
[build-dependencies] [build-dependencies]
cc = { version = "1.0" } cc = { version = "1.0" }
pkg-config = { version = "0.3.17" } pkg-config = { version = "0.3.17" }
lua-src = { version = ">= 544.0.0, < 550.0.0", optional = true } lua-src = { version = ">= 546.0.0, < 546.1.0", optional = true }
luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true } luajit-src = { version = ">= 210.4.0, < 210.5.0", optional = true }
luau0-src = { version = "0.3.6", optional = true } luau0-src = { version = "0.7.0", optional = true }
[dev-dependencies] [dev-dependencies]
rustyline = "10.0" rustyline = "10.0"
+1 -1
View File
@@ -7,7 +7,7 @@
[crates.io]: https://crates.io/crates/mlua [crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg [API Documentation]: https://docs.rs/mlua/badge.svg
[docs.rs]: https://docs.rs/mlua [docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG [Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/v0.8/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua [codecov.io]: https://codecov.io/gh/khvzak/mlua
[MSRV]: https://img.shields.io/badge/rust-1.56+-brightgreen.svg?&logo=rust [MSRV]: https://img.shields.io/badge/rust-1.56+-brightgreen.svg?&logo=rust
+1
View File
@@ -228,6 +228,7 @@ impl Compiler {
coverageLevel: self.coverage_level as c_int, coverageLevel: self.coverage_level as c_int,
vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()), vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()),
vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()), vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()),
vectorType: ptr::null(),
mutableGlobals: mutable_globals_ptr, mutableGlobals: mutable_globals_ptr,
}; };
ffi::luau_compile(source.as_ref(), options) ffi::luau_compile(source.as_ref(), options)
+3
View File
@@ -112,7 +112,10 @@ extern "C" {
pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State; pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State;
pub fn lua_close(L: *mut lua_State); pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State; pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
// Deprecated in Lua 5.4.6
pub fn lua_resetthread(L: *mut lua_State) -> c_int; pub fn lua_resetthread(L: *mut lua_State) -> c_int;
#[cfg(feature = "vendored")]
pub fn lua_closethread(L: *mut lua_State, from: *mut lua_State) -> c_int;
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction; pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
+2 -2
View File
@@ -341,7 +341,7 @@ pub unsafe fn luaL_loadbufferx(
fn free(p: *mut c_void); fn free(p: *mut c_void);
} }
let chunk_is_text = (*data as u8) >= b'\n'; let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
if !mode.is_null() { if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes(); let modeb = CStr::from_ptr(mode).to_bytes();
if !chunk_is_text && !modeb.contains(&b'b') { if !chunk_is_text && !modeb.contains(&b'b') {
@@ -423,7 +423,7 @@ pub unsafe fn luaL_traceback(
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else { } else {
lua_getinfo(L1, level, cstr!("sln"), &mut ar); lua_getinfo(L1, level, cstr!("sln"), &mut ar);
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src.as_ptr()); lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src);
if ar.currentline > 0 { if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline); lua_pushfstring(L, cstr!("%d:"), ar.currentline);
} }
+10 -4
View File
@@ -7,12 +7,15 @@ use std::ptr;
// Option for multiple returns in 'lua_pcall' and 'lua_call' // Option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1; pub const LUA_MULTRET: c_int = -1;
// Max number of Lua stack slots
const LUAI_MAXCSTACK: c_int = 100000;
// //
// Pseudo-indices // Pseudo-indices
// //
pub const LUA_REGISTRYINDEX: c_int = -10000; pub const LUA_REGISTRYINDEX: c_int = -LUAI_MAXCSTACK - 2000;
pub const LUA_ENVIRONINDEX: c_int = -10001; pub const LUA_ENVIRONINDEX: c_int = -LUAI_MAXCSTACK - 2001;
pub const LUA_GLOBALSINDEX: c_int = -10002; pub const LUA_GLOBALSINDEX: c_int = -LUAI_MAXCSTACK - 2002;
pub const fn lua_upvalueindex(i: c_int) -> c_int { pub const fn lua_upvalueindex(i: c_int) -> c_int {
LUA_GLOBALSINDEX - i LUA_GLOBALSINDEX - i
@@ -257,6 +260,7 @@ extern "C" {
extern "C" { extern "C" {
pub fn lua_error(L: *mut lua_State) -> !; pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; 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;
pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_concat(L: *mut lua_State, n: c_int);
// TODO: lua_encodepointer // TODO: lua_encodepointer
pub fn lua_clock() -> c_double; pub fn lua_clock() -> c_double;
@@ -267,6 +271,7 @@ extern "C" {
dtor: Option<unsafe extern "C" fn(*mut lua_State, *mut c_void)>, dtor: Option<unsafe extern "C" fn(*mut lua_State, *mut c_void)>,
); );
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int); pub fn lua_clonefunction(L: *mut lua_State, idx: c_int);
pub fn lua_cleartable(L: *mut lua_State, idx: c_int);
} }
// //
@@ -460,13 +465,14 @@ pub struct lua_Debug {
pub name: *const c_char, pub name: *const c_char,
pub what: *const c_char, pub what: *const c_char,
pub source: *const c_char, pub source: *const c_char,
pub short_src: *const c_char,
pub linedefined: c_int, pub linedefined: c_int,
pub currentline: c_int, pub currentline: c_int,
pub nupvals: u8, pub nupvals: u8,
pub nparams: u8, pub nparams: u8,
pub isvararg: c_char, pub isvararg: c_char,
pub short_src: [c_char; LUA_IDSIZE],
pub userdata: *mut c_void, pub userdata: *mut c_void,
pub ssbuf: [c_char; LUA_IDSIZE],
} }
// //
+2 -1
View File
@@ -10,7 +10,8 @@ pub struct lua_CompileOptions {
pub coverageLevel: c_int, pub coverageLevel: c_int,
pub vectorLib: *const c_char, pub vectorLib: *const c_char,
pub vectorCtor: *const c_char, pub vectorCtor: *const c_char,
pub mutableGlobals: *mut *const c_char, pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
} }
extern "C" { extern "C" {
+3 -1
View File
@@ -79,9 +79,11 @@ pub(crate) fn keep_lua_symbols() {
symbols.push(lua_tocfunction as _); symbols.push(lua_tocfunction as _);
symbols.push(luaL_loadstring as _); symbols.push(luaL_loadstring as _);
symbols.push(luaL_openlibs as _); symbols.push(luaL_openlibs as _);
if cfg!(any(feature = "lua54", feature = "lua53", feature = "lua52")) { #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
symbols.push(lua_getglobal as _); symbols.push(lua_getglobal as _);
symbols.push(lua_setglobal as _); symbols.push(lua_setglobal as _);
symbols.push(luaL_setfuncs as _);
} }
} }
+8 -6
View File
@@ -114,7 +114,7 @@ impl<'lua> Function<'lua> {
R::from_lua_multi(results, lua) R::from_lua_multi(results, lua)
} }
/// Returns a Feature that, when polled, calls `self`, passing `args` as function arguments, /// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution. /// and drives the execution.
/// ///
/// Internally it wraps the function to an [`AsyncThread`]. /// Internally it wraps the function to an [`AsyncThread`].
@@ -273,10 +273,13 @@ impl<'lua> Function<'lua> {
name_what: None, name_what: None,
what: ptr_to_cstr_bytes(ar.what).map(|s| s.to_vec()), what: ptr_to_cstr_bytes(ar.what).map(|s| s.to_vec()),
source: ptr_to_cstr_bytes(ar.source).map(|s| s.to_vec()), source: ptr_to_cstr_bytes(ar.source).map(|s| s.to_vec()),
short_src: ptr_to_cstr_bytes(&ar.short_src as *const _).map(|s| s.to_vec()),
line_defined: ar.linedefined as i32,
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
last_line_defined: ar.lastlinedefined as i32, short_src: ptr_to_cstr_bytes(ar.short_src.as_ptr()).map(|s| s.to_vec()),
#[cfg(feature = "luau")]
short_src: ptr_to_cstr_bytes(ar.short_src).map(|s| s.to_vec()),
line_defined: ar.linedefined,
#[cfg(not(feature = "luau"))]
last_line_defined: ar.lastlinedefined,
} }
} }
} }
@@ -312,8 +315,7 @@ impl<'lua> Function<'lua> {
lua.push_ref(&self.0); lua.push_ref(&self.0);
let data_ptr = &mut data as *mut Vec<u8> as *mut c_void; let data_ptr = &mut data as *mut Vec<u8> as *mut c_void;
let strip = if strip { 1 } else { 0 }; ffi::lua_dump(lua.state, writer, data_ptr, strip as i32);
ffi::lua_dump(lua.state, writer, data_ptr, strip);
ffi::lua_pop(lua.state, 1); ffi::lua_pop(lua.state, 1);
} }
+9 -6
View File
@@ -63,7 +63,7 @@ impl<'lua> Debug<'lua> {
} }
/// Corresponds to the `n` what mask. /// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames<'lua> { pub fn names(&self) -> DebugNames {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
@@ -87,7 +87,7 @@ impl<'lua> Debug<'lua> {
} }
/// Corresponds to the `S` what mask. /// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource<'lua> { pub fn source(&self) -> DebugSource {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
@@ -102,10 +102,13 @@ impl<'lua> Debug<'lua> {
DebugSource { DebugSource {
source: ptr_to_cstr_bytes((*self.ar.get()).source), source: ptr_to_cstr_bytes((*self.ar.get()).source),
short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src.as_ptr()),
line_defined: (*self.ar.get()).linedefined as i32,
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
last_line_defined: (*self.ar.get()).lastlinedefined as i32, short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src.as_ptr()),
#[cfg(feature = "luau")]
short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src),
line_defined: (*self.ar.get()).linedefined,
#[cfg(not(feature = "luau"))]
last_line_defined: (*self.ar.get()).lastlinedefined,
what: ptr_to_cstr_bytes((*self.ar.get()).what), what: ptr_to_cstr_bytes((*self.ar.get()).what),
} }
} }
@@ -125,7 +128,7 @@ impl<'lua> Debug<'lua> {
"lua_getinfo failed with `l`" "lua_getinfo failed with `l`"
); );
(*self.ar.get()).currentline as i32 (*self.ar.get()).currentline
} }
} }
-2
View File
@@ -71,8 +71,6 @@
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html //! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html //! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.8.2")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any* // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all. // warnings at all.
#![doc(test(attr(deny(warnings))))] #![doc(test(attr(deny(warnings))))]
+114 -67
View File
@@ -88,6 +88,8 @@ pub(crate) struct ExtraData {
registered_userdata: FxHashMap<TypeId, c_int>, registered_userdata: FxHashMap<TypeId, c_int>,
registered_userdata_mt: FxHashMap<*const c_void, Option<TypeId>>, registered_userdata_mt: FxHashMap<*const c_void, Option<TypeId>>,
// When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>, registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
@@ -402,6 +404,11 @@ impl Lua {
return ptr::null_mut(); return ptr::null_mut();
} }
// Do not allocate more than isize::MAX
if nsize > isize::MAX as usize {
return ptr::null_mut();
}
// Are we fit to the memory limits? // Are we fit to the memory limits?
let mut mem_diff = nsize as isize; let mut mem_diff = nsize as isize;
if !ptr.is_null() { if !ptr.is_null() {
@@ -411,12 +418,14 @@ impl Lua {
if mem_info.memory_limit > 0 && new_used_memory > mem_info.memory_limit { if mem_info.memory_limit > 0 && new_used_memory > mem_info.memory_limit {
return ptr::null_mut(); return ptr::null_mut();
} }
let new_layout = Layout::from_size_align_unchecked(nsize, ffi::SYS_MIN_ALIGN);
mem_info.used_memory += mem_diff; mem_info.used_memory += mem_diff;
if ptr.is_null() { if ptr.is_null() {
// Allocate new memory // Allocate new memory
let new_layout = match Layout::from_size_align(nsize, ffi::SYS_MIN_ALIGN) {
Ok(layout) => layout,
Err(_) => return ptr::null_mut(),
};
let new_ptr = alloc::alloc(new_layout) as *mut c_void; let new_ptr = alloc::alloc(new_layout) as *mut c_void;
if new_ptr.is_null() { if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout); alloc::handle_alloc_error(new_layout);
@@ -428,7 +437,7 @@ impl Lua {
let old_layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN); let old_layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN);
let new_ptr = alloc::realloc(ptr as *mut u8, old_layout, nsize) as *mut c_void; let new_ptr = alloc::realloc(ptr as *mut u8, old_layout, nsize) as *mut c_void;
if new_ptr.is_null() { if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout); alloc::handle_alloc_error(old_layout);
} }
new_ptr new_ptr
} }
@@ -437,12 +446,19 @@ impl Lua {
let use_rust_allocator = !(cfg!(feature = "luajit") && cfg!(not(feature = "vendored"))); let use_rust_allocator = !(cfg!(feature = "luajit") && cfg!(not(feature = "vendored")));
let (state, mem_info) = if use_rust_allocator { let (state, mem_info) = if use_rust_allocator {
let mem_info = Box::into_raw(Box::new(MemoryInfo::default())); let mut mem_info = Box::into_raw(Box::new(MemoryInfo::default()));
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void); let mut state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
// If state is null (it's possible for LuaJIT on non-x86 arch) then switch to Lua internal allocator
if state.is_null() {
drop(Box::from_raw(mem_info));
mem_info = ptr::null_mut();
state = ffi::luaL_newstate();
}
(state, mem_info) (state, mem_info)
} else { } else {
(ffi::luaL_newstate(), ptr::null_mut()) (ffi::luaL_newstate(), ptr::null_mut())
}; };
assert!(!state.is_null(), "Failed to instantiate Lua VM");
ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1); ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1);
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
@@ -496,6 +512,7 @@ impl Lua {
/// by calling this function again. /// by calling this function again.
#[allow(clippy::missing_safety_doc)] #[allow(clippy::missing_safety_doc)]
pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua { pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua {
assert!(!state.is_null(), "Lua state is NULL");
let main_state = get_main_state(state).unwrap_or(state); let main_state = get_main_state(state).unwrap_or(state);
let main_state_top = ffi::lua_gettop(main_state); let main_state_top = ffi::lua_gettop(main_state);
@@ -874,10 +891,8 @@ impl Lua {
ffi::luaL_sandboxthread(state); ffi::luaL_sandboxthread(state);
} else { } else {
// Restore original `LUA_GLOBALSINDEX` // Restore original `LUA_GLOBALSINDEX`
self.ref_thread_exec(|ref_thread| { ffi::lua_xpush(self.ref_thread(), state, ffi::LUA_GLOBALSINDEX);
ffi::lua_xpush(ref_thread, state, ffi::LUA_GLOBALSINDEX); ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
});
ffi::luaL_sandbox(state, 0); ffi::luaL_sandbox(state, 0);
} }
})?; })?;
@@ -1116,7 +1131,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn warning<S: Into<Vec<u8>>>(&self, msg: S, tocont: bool) -> Result<()> { pub fn warning<S: Into<Vec<u8>>>(&self, msg: S, tocont: bool) -> Result<()> {
let msg = CString::new(msg).map_err(|err| Error::RuntimeError(err.to_string()))?; let msg = CString::new(msg).map_err(|err| Error::RuntimeError(err.to_string()))?;
unsafe { ffi::lua_warning(self.state, msg.as_ptr(), if tocont { 1 } else { 0 }) }; unsafe { ffi::lua_warning(self.state, msg.as_ptr(), tocont as c_int) };
Ok(()) Ok(())
} }
@@ -1358,9 +1373,8 @@ impl Lua {
where where
S: AsChunk<'lua> + ?Sized, S: AsChunk<'lua> + ?Sized,
{ {
let name = chunk let caller = Location::caller();
.name() let name = chunk.name().unwrap_or_else(|| caller.to_string());
.unwrap_or_else(|| Location::caller().to_string());
Chunk { Chunk {
lua: self, lua: self,
@@ -1420,11 +1434,15 @@ impl Lua {
S: AsRef<[u8]> + ?Sized, S: AsRef<[u8]> + ?Sized,
{ {
unsafe { unsafe {
if self.unlikely_memory_error() {
push_string(self.ref_thread(), s.as_ref(), false)?;
return Ok(String(self.pop_ref_thread()));
}
let _sg = StackGuard::new(self.state); let _sg = StackGuard::new(self.state);
check_stack(self.state, 3)?; check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error(); push_string(self.state, s.as_ref(), true)?;
push_string(self.state, s, protect)?;
Ok(String(self.pop_ref())) Ok(String(self.pop_ref()))
} }
} }
@@ -1440,11 +1458,15 @@ impl Lua {
/// Lua may use these hints to preallocate memory for the new table. /// Lua may use these hints to preallocate memory for the new table.
pub fn create_table_with_capacity(&self, narr: c_int, nrec: c_int) -> Result<Table> { pub fn create_table_with_capacity(&self, narr: c_int, nrec: c_int) -> Result<Table> {
unsafe { unsafe {
if self.unlikely_memory_error() {
push_table(self.ref_thread(), narr, nrec, false)?;
return Ok(Table(self.pop_ref_thread()));
}
let _sg = StackGuard::new(self.state); let _sg = StackGuard::new(self.state);
check_stack(self.state, 3)?; check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error(); push_table(self.state, narr, nrec, true)?;
push_table(self.state, narr, nrec, protect)?;
Ok(Table(self.pop_ref())) Ok(Table(self.pop_ref()))
} }
} }
@@ -1712,15 +1734,18 @@ impl Lua {
all(feature = "luajit", feature = "vendored"), all(feature = "luajit", feature = "vendored"),
feature = "luau", feature = "luau",
))] ))]
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) { pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool {
let extra = &mut *self.extra.get(); let extra = &mut *self.extra.get();
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
if extra.recycled_thread_cache.len() < extra.recycled_thread_cache.capacity() { if extra.recycled_thread_cache.len() < extra.recycled_thread_cache.capacity() {
#[cfg(feature = "lua54")] 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); 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")] #[cfg(feature = "lua54")]
if status != ffi::LUA_OK { if status != ffi::LUA_OK {
return; // Error object is on top, drop it
ffi::lua_settop(thread_state, 0);
} }
#[cfg(all(feature = "luajit", feature = "vendored"))] #[cfg(all(feature = "luajit", feature = "vendored"))]
ffi::lua_resetthread(self.state, thread_state); ffi::lua_resetthread(self.state, thread_state);
@@ -1728,7 +1753,9 @@ impl Lua {
ffi::lua_resetthread(thread_state); ffi::lua_resetthread(thread_state);
extra.recycled_thread_cache.push(thread.0.index); extra.recycled_thread_cache.push(thread.0.index);
thread.0.index = 0; thread.0.index = 0;
return true;
} }
false
} }
/// Create a Lua userdata object from a custom userdata type. /// Create a Lua userdata object from a custom userdata type.
@@ -2005,7 +2032,7 @@ impl Lua {
check_stack(self.state, 3)?; check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error(); let protect = !self.unlikely_memory_error();
push_string(self.state, name, protect)?; push_string(self.state, name.as_ref(), protect)?;
ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX); ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX);
self.pop_value() self.pop_value()
@@ -2037,34 +2064,38 @@ impl Lua {
/// [`RegistryKey`]: crate::RegistryKey /// [`RegistryKey`]: crate::RegistryKey
pub fn create_registry_value<'lua, T: ToLua<'lua>>(&'lua self, t: T) -> Result<RegistryKey> { pub fn create_registry_value<'lua, T: ToLua<'lua>>(&'lua self, t: T) -> Result<RegistryKey> {
let t = t.to_lua(self)?; let t = t.to_lua(self)?;
if t == Value::Nil {
// Special case to skip calling `luaL_ref` and use `LUA_REFNIL` instead
let unref_list = unsafe { (*self.extra.get()).registry_unref_list.clone() };
return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list));
}
unsafe { unsafe {
let _sg = StackGuard::new(self.state); let _sg = StackGuard::new(self.state);
check_stack(self.state, 4)?; check_stack(self.state, 4)?;
let unref_list = (*self.extra.get()).registry_unref_list.clone();
self.push_value(t)?; self.push_value(t)?;
// Try to reuse previously allocated RegistryKey // Try to reuse previously allocated slot
let unref_list2 = unref_list.clone(); let unref_list = (*self.extra.get()).registry_unref_list.clone();
let mut unref_list2 = mlua_expect!(unref_list2.lock(), "unref list poisoned"); let free_registry_id = mlua_expect!(unref_list.lock(), "unref list poisoned")
if let Some(registry_id) = unref_list2.as_mut().and_then(|x| x.pop()) { .as_mut()
.and_then(|x| x.pop());
if let Some(registry_id) = free_registry_id {
// It must be safe to replace the value without triggering memory error // It must be safe to replace the value without triggering memory error
ffi::lua_rawseti(self.state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); ffi::lua_rawseti(self.state, ffi::LUA_REGISTRYINDEX, registry_id as Integer);
return Ok(RegistryKey { return Ok(RegistryKey::new(registry_id, unref_list));
registry_id,
unref_list,
});
} }
// Allocate a new RegistryKey // Allocate a new RegistryKey
let registry_id = protect_lua!(self.state, 1, 0, |state| { let registry_id = if self.unlikely_memory_error() {
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX) ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX)
})?; } else {
protect_lua!(self.state, 1, 0, |state| {
Ok(RegistryKey { ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
registry_id, })?
unref_list, };
}) Ok(RegistryKey::new(registry_id, unref_list))
} }
} }
@@ -2079,16 +2110,16 @@ impl Lua {
return Err(Error::MismatchedRegistryKey); return Err(Error::MismatchedRegistryKey);
} }
let value = unsafe { let value = match key.is_nil() {
let _sg = StackGuard::new(self.state); true => Value::Nil,
check_stack(self.state, 1)?; false => unsafe {
let _sg = StackGuard::new(self.state);
check_stack(self.state, 1)?;
ffi::lua_rawgeti( let id = key.registry_id as Integer;
self.state, ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, id);
ffi::LUA_REGISTRYINDEX, self.pop_value()
key.registry_id as Integer, },
);
self.pop_value()
}; };
T::from_lua(value, self) T::from_lua(value, self)
} }
@@ -2127,20 +2158,31 @@ impl Lua {
} }
let t = t.to_lua(self)?; let t = t.to_lua(self)?;
if t == Value::Nil && key.is_nil() {
// Nothing to replace
return Ok(());
} else if t != Value::Nil && key.registry_id == ffi::LUA_REFNIL {
// We cannot update `LUA_REFNIL` slot
let err = "cannot replace nil value with non-nil".to_string();
return Err(Error::RuntimeError(err));
}
unsafe { unsafe {
let _sg = StackGuard::new(self.state); let _sg = StackGuard::new(self.state);
check_stack(self.state, 2)?; check_stack(self.state, 2)?;
self.push_value(t)?; let id = key.registry_id as Integer;
if t == Value::Nil {
self.push_value(Value::Integer(id))?;
key.set_nil(true);
} else {
self.push_value(t)?;
key.set_nil(false);
}
// It must be safe to replace the value without triggering memory error // It must be safe to replace the value without triggering memory error
ffi::lua_rawseti( ffi::lua_rawseti(self.state, ffi::LUA_REGISTRYINDEX, id);
self.state,
ffi::LUA_REGISTRYINDEX,
key.registry_id as Integer,
);
Ok(())
} }
Ok(())
} }
/// Returns true if the given `RegistryKey` was created by a `Lua` which shares the underlying /// Returns true if the given `RegistryKey` was created by a `Lua` which shares the underlying
@@ -2252,7 +2294,7 @@ impl Lua {
} }
Value::Boolean(b) => { Value::Boolean(b) => {
ffi::lua_pushboolean(self.state, if b { 1 } else { 0 }); ffi::lua_pushboolean(self.state, b as c_int);
} }
Value::LightUserData(ud) => { Value::LightUserData(ud) => {
@@ -2428,6 +2470,13 @@ impl Lua {
LuaRef { lua: self, index } LuaRef { lua: self, index }
} }
// Same as `pop_ref` but assumes the value is already on the reference thread
pub(crate) unsafe fn pop_ref_thread(&self) -> LuaRef {
let extra = &mut *self.extra.get();
let index = ref_stack_pop(extra);
LuaRef { lua: self, index }
}
pub(crate) fn clone_ref<'lua>(&'lua self, lref: &LuaRef<'lua>) -> LuaRef<'lua> { pub(crate) fn clone_ref<'lua>(&'lua self, lref: &LuaRef<'lua>) -> LuaRef<'lua> {
unsafe { unsafe {
let extra = &mut *self.extra.get(); let extra = &mut *self.extra.get();
@@ -2446,16 +2495,6 @@ impl Lua {
} }
} }
/// Executes the function provided on the ref thread
#[inline]
pub(crate) unsafe fn ref_thread_exec<F, R>(&self, f: F) -> R
where
F: FnOnce(*mut ffi::lua_State) -> R,
{
let ref_thread = (*self.extra.get()).ref_thread;
f(ref_thread)
}
unsafe fn push_userdata_metatable<T: 'static + UserData>(&self) -> Result<()> { unsafe fn push_userdata_metatable<T: 'static + UserData>(&self) -> Result<()> {
let extra = &mut *self.extra.get(); let extra = &mut *self.extra.get();
@@ -2580,6 +2619,7 @@ impl Lua {
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<Option<TypeId>> { pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<Option<TypeId>> {
self.push_ref(lref); self.push_ref(lref);
if ffi::lua_getmetatable(self.state, -1) == 0 { if ffi::lua_getmetatable(self.state, -1) == 0 {
ffi::lua_pop(self.state, 1);
return Err(Error::UserDataTypeMismatch); return Err(Error::UserDataTypeMismatch);
} }
let mt_ptr = ffi::lua_topointer(self.state, -1); let mt_ptr = ffi::lua_topointer(self.state, -1);
@@ -2957,6 +2997,13 @@ impl Lua {
} }
} }
impl LuaInner {
#[inline(always)]
pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State {
unsafe { (*self.extra.get()).ref_thread }
}
}
struct StateGuard<'a>(&'a mut LuaInner, *mut ffi::lua_State); struct StateGuard<'a>(&'a mut LuaInner, *mut ffi::lua_State);
impl<'a> StateGuard<'a> { impl<'a> StateGuard<'a> {
+6
View File
@@ -22,6 +22,12 @@ impl Lua {
globals.raw_set("require", self.create_function(lua_require)?)?; globals.raw_set("require", self.create_function(lua_require)?)?;
globals.raw_set("vector", self.create_c_function(lua_vector)?)?; globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
if let Some(version) = option_env!("LUAU_VERSION") {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
Ok(()) Ok(())
} }
} }
+24 -2
View File
@@ -71,7 +71,7 @@ impl Options {
/// ///
/// [`deny_recursive_tables`]: #structfield.deny_recursive_tables /// [`deny_recursive_tables`]: #structfield.deny_recursive_tables
#[must_use] #[must_use]
pub fn deny_recursive_tables(mut self, enabled: bool) -> Self { pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self {
self.deny_recursive_tables = enabled; self.deny_recursive_tables = enabled;
self self
} }
@@ -327,9 +327,31 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
visitor.visit_newtype_struct(self) visitor.visit_newtype_struct(self)
} }
#[inline]
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(),
_ => self.deserialize_any(visitor),
}
}
#[inline]
fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(),
_ => self.deserialize_any(visitor),
}
}
serde::forward_to_deserialize_any! { serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
byte_buf unit unit_struct identifier ignored_any byte_buf identifier ignored_any
} }
} }
+3 -7
View File
@@ -10,7 +10,7 @@ use crate::ffi;
use crate::lua::Lua; use crate::lua::Lua;
use crate::table::Table; use crate::table::Table;
use crate::types::LightUserData; use crate::types::LightUserData;
use crate::util::{assert_stack, check_stack, StackGuard}; use crate::util::check_stack;
use crate::value::Value; use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde. /// Trait for serializing/deserializing Lua values using Serde.
@@ -205,12 +205,8 @@ impl<'lua> LuaSerdeExt<'lua> for Lua {
fn array_metatable(&'lua self) -> Table<'lua> { fn array_metatable(&'lua self) -> Table<'lua> {
unsafe { unsafe {
let _sg = StackGuard::new(self.state); push_array_metatable(self.ref_thread());
assert_stack(self.state, 1); Table(self.pop_ref_thread())
push_array_metatable(self.state);
Table(self.pop_ref())
} }
} }
+8 -10
View File
@@ -13,7 +13,6 @@ use {
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi; use crate::ffi;
use crate::types::LuaRef; use crate::types::LuaRef;
use crate::util::{assert_stack, StackGuard};
/// Handle to an internal Lua string. /// Handle to an internal Lua string.
/// ///
@@ -40,6 +39,7 @@ impl<'lua> String<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn to_str(&self) -> Result<&str> { pub fn to_str(&self) -> Result<&str> {
str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError { str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError {
from: "string", from: "string",
@@ -66,6 +66,7 @@ impl<'lua> String<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn to_string_lossy(&self) -> Cow<'_, str> { pub fn to_string_lossy(&self) -> Cow<'_, str> {
StdString::from_utf8_lossy(self.as_bytes()) StdString::from_utf8_lossy(self.as_bytes())
} }
@@ -87,6 +88,7 @@ impl<'lua> String<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn as_bytes(&self) -> &[u8] { pub fn as_bytes(&self) -> &[u8] {
let nulled = self.as_bytes_with_nul(); let nulled = self.as_bytes_with_nul();
&nulled[..nulled.len() - 1] &nulled[..nulled.len() - 1]
@@ -94,21 +96,17 @@ impl<'lua> String<'lua> {
/// Get the bytes that make up this string, including the trailing nul byte. /// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> &[u8] { pub fn as_bytes_with_nul(&self) -> &[u8] {
let lua = self.0.lua; let ref_thread = self.0.lua.ref_thread();
unsafe { unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
mlua_debug_assert!( mlua_debug_assert!(
ffi::lua_type(lua.state, -1) == ffi::LUA_TSTRING, ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING,
"string ref is not string type" "string ref is not string type"
); );
let mut size = 0; let mut size = 0;
// This will not trigger a 'm' error, because the reference is guaranteed to be of // This will not trigger a 'm' error, because the reference is guaranteed to be of
// string type // string type
let data = ffi::lua_tolstring(lua.state, -1, &mut size); 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 + 1)
} }
@@ -121,8 +119,8 @@ impl<'lua> String<'lua> {
/// Typically this function is used only for hashing and debug information. /// Typically this function is used only for hashing and debug information.
#[inline] #[inline]
pub fn to_pointer(&self) -> *const c_void { pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua; let ref_thread = self.0.lua.ref_thread();
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) } unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
} }
} }
+152 -25
View File
@@ -58,6 +58,11 @@ impl<'lua> Table<'lua> {
/// ///
/// [`raw_set`]: #method.raw_set /// [`raw_set`]: #method.raw_set
pub fn set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { pub fn set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
// Fast track
if !self.has_metatable() {
return self.raw_set(key, value);
}
let lua = self.0.lua; let lua = self.0.lua;
let key = key.to_lua(lua)?; let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?; let value = value.to_lua(lua)?;
@@ -98,6 +103,11 @@ impl<'lua> Table<'lua> {
/// ///
/// [`raw_get`]: #method.raw_get /// [`raw_get`]: #method.raw_get
pub fn get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> { pub fn get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
// Fast track
if !self.has_metatable() {
return self.raw_get(key);
}
let lua = self.0.lua; let lua = self.0.lua;
let key = key.to_lua(lua)?; let key = key.to_lua(lua)?;
@@ -116,18 +126,54 @@ impl<'lua> Table<'lua> {
/// Checks whether the table contains a non-nil value for `key`. /// Checks whether the table contains a non-nil value for `key`.
pub fn contains_key<K: ToLua<'lua>>(&self, key: K) -> Result<bool> { pub fn contains_key<K: ToLua<'lua>>(&self, key: K) -> Result<bool> {
let lua = self.0.lua; Ok(self.get::<_, Value>(key)? != Value::Nil)
let key = key.to_lua(lua)?; }
/// Appends a value to the back of the table.
pub fn push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
// Fast track
if !self.has_metatable() {
return self.raw_push(value);
}
let lua = self.0.lua;
let value = value.to_lua(lua)?;
unsafe { unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?; check_stack(lua.state, 4)?;
lua.push_ref(&self.0); lua.push_ref(&self.0);
lua.push_value(key)?; lua.push_value(value)?;
protect_lua!(lua.state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?; protect_lua!(lua.state, 2, 0, fn(state) {
Ok(ffi::lua_isnil(lua.state, -1) == 0) let len = ffi::luaL_len(state, -2) as Integer;
ffi::lua_seti(state, -2, len + 1);
})?
} }
Ok(())
}
/// Removes the last element from the table and returns it.
pub fn pop<V: FromLua<'lua>>(&self) -> Result<V> {
// Fast track
if !self.has_metatable() {
return self.raw_pop();
}
let lua = self.0.lua;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
protect_lua!(lua.state, 1, 1, fn(state) {
let len = ffi::luaL_len(state, -1) as Integer;
ffi::lua_geti(state, -1, len);
ffi::lua_pushnil(state);
ffi::lua_seti(state, -3, len);
})?;
lua.pop_value()
};
V::from_lua(value, lua)
} }
/// Compares two tables for equality. /// Compares two tables for equality.
@@ -188,6 +234,9 @@ impl<'lua> Table<'lua> {
/// Sets a key-value pair without invoking metamethods. /// Sets a key-value pair without invoking metamethods.
pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua; let lua = self.0.lua;
let key = key.to_lua(lua)?; let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?; let value = value.to_lua(lua)?;
@@ -199,6 +248,7 @@ impl<'lua> Table<'lua> {
lua.push_ref(&self.0); lua.push_ref(&self.0);
lua.push_value(key)?; lua.push_value(key)?;
lua.push_value(value)?; lua.push_value(value)?;
if lua.unlikely_memory_error() { if lua.unlikely_memory_error() {
ffi::lua_rawset(lua.state, -3); ffi::lua_rawset(lua.state, -3);
ffi::lua_pop(lua.state, 1); ffi::lua_pop(lua.state, 1);
@@ -254,6 +304,56 @@ impl<'lua> Table<'lua> {
} }
} }
/// Appends a value to the back of the table without invoking metamethods.
pub fn raw_push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let value = value.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
unsafe fn callback(state: *mut ffi::lua_State) {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
}
if lua.unlikely_memory_error() {
callback(lua.state);
} else {
protect_lua!(lua.state, 2, 0, fn(state) callback(state))?;
}
}
Ok(())
}
/// Removes the last element from the table and returns it, without invoking metamethods.
pub fn raw_pop<V: FromLua<'lua>>(&self) -> Result<V> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(lua.state, -1) as Integer;
ffi::lua_rawgeti(lua.state, -1, len);
// Set slot to nil (it must be safe to do)
ffi::lua_pushnil(lua.state);
ffi::lua_rawseti(lua.state, -3, len);
lua.pop_value()
};
V::from_lua(value, lua)
}
/// Removes a key from the table. /// Removes a key from the table.
/// ///
/// If `key` is an integer, mlua shifts down the elements from `table[key+1]`, /// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
@@ -295,6 +395,11 @@ impl<'lua> Table<'lua> {
/// ///
/// [`raw_len`]: #method.raw_len /// [`raw_len`]: #method.raw_len
pub fn len(&self) -> Result<Integer> { pub fn len(&self) -> Result<Integer> {
// Fast track
if !self.has_metatable() {
return Ok(self.raw_len());
}
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(lua.state);
@@ -307,14 +412,8 @@ impl<'lua> Table<'lua> {
/// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod. /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod.
pub fn raw_len(&self) -> Integer { pub fn raw_len(&self) -> Integer {
let lua = self.0.lua; let ref_thread = self.0.lua.ref_thread();
unsafe { unsafe { ffi::lua_rawlen(ref_thread, self.0.index) as Integer }
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
ffi::lua_rawlen(lua.state, -1) as Integer
}
} }
/// Returns a reference to the metatable of this table, or `None` if no metatable is set. /// Returns a reference to the metatable of this table, or `None` if no metatable is set.
@@ -340,6 +439,12 @@ impl<'lua> Table<'lua> {
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
/// nothing). /// nothing).
pub fn set_metatable(&self, metatable: Option<Table<'lua>>) { pub fn set_metatable(&self, metatable: Option<Table<'lua>>) {
// Workaround to throw readonly error without returning Result
#[cfg(feature = "luau")]
if self.is_readonly() {
panic!("attempt to modify a readonly table");
}
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(lua.state);
@@ -355,21 +460,33 @@ impl<'lua> Table<'lua> {
} }
} }
/// Returns true if the table has metatable attached.
#[doc(hidden)]
#[inline]
pub fn has_metatable(&self) -> bool {
let ref_thread = self.0.lua.ref_thread();
unsafe {
if ffi::lua_getmetatable(ref_thread, self.0.index) != 0 {
ffi::lua_pop(ref_thread, 1);
return true;
}
}
false
}
/// Sets `readonly` attribute on the table. /// Sets `readonly` attribute on the table.
/// ///
/// Requires `feature = "luau"` /// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_readonly(&self, enabled: bool) { pub fn set_readonly(&self, enabled: bool) {
let lua = self.0.lua; let ref_thread = self.0.lua.ref_thread();
unsafe { unsafe {
lua.ref_thread_exec(|refthr| { ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _);
ffi::lua_setreadonly(refthr, self.0.index, enabled as _); if !enabled {
if !enabled { // Reset "safeenv" flag
// Reset "safeenv" flag ffi::lua_setsafeenv(ref_thread, self.0.index, 0);
ffi::lua_setsafeenv(refthr, self.0.index, 0); }
}
});
} }
} }
@@ -379,8 +496,8 @@ impl<'lua> Table<'lua> {
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn is_readonly(&self) -> bool { pub fn is_readonly(&self) -> bool {
let lua = self.0.lua; let ref_thread = self.0.lua.ref_thread();
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_getreadonly(refthr, self.0.index) != 0) } unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
} }
/// Converts the table to a generic C pointer. /// Converts the table to a generic C pointer.
@@ -391,8 +508,8 @@ impl<'lua> Table<'lua> {
/// Typically this function is used only for hashing and debug information. /// Typically this function is used only for hashing and debug information.
#[inline] #[inline]
pub fn to_pointer(&self) -> *const c_void { pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua; let ref_thread = self.0.lua.ref_thread();
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) } unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
} }
/// Consume this table and return an iterator over the pairs of the table. /// Consume this table and return an iterator over the pairs of the table.
@@ -532,6 +649,16 @@ impl<'lua> Table<'lua> {
ffi::lua_rawequal(lua.state, -1, -2) != 0 ffi::lua_rawequal(lua.state, -1, -2) != 0
} }
} }
#[cfg(feature = "luau")]
#[inline(always)]
pub(crate) fn check_readonly_write(&self) -> Result<()> {
if self.is_readonly() {
let err = "attempt to modify a readonly table".to_string();
return Err(Error::RuntimeError(err));
}
Ok(())
}
} }
impl<'lua> PartialEq for Table<'lua> { impl<'lua> PartialEq for Table<'lua> {
+22 -10
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi; use crate::ffi;
use crate::types::LuaRef; use crate::types::LuaRef;
use crate::util::{check_stack, error_traceback, pop_error, StackGuard}; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
use crate::value::{FromLuaMulti, ToLuaMulti}; use crate::value::{FromLuaMulti, ToLuaMulti};
#[cfg(any( #[cfg(any(
@@ -118,8 +118,7 @@ impl<'lua> Thread<'lua> {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(lua.state);
check_stack(lua.state, cmp::max(nargs + 1, 3))?; check_stack(lua.state, cmp::max(nargs + 1, 3))?;
let thread_state = let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let status = ffi::lua_status(thread_state); let status = ffi::lua_status(thread_state);
if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 { if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 {
@@ -136,8 +135,12 @@ impl<'lua> Thread<'lua> {
let ret = ffi::lua_resume(thread_state, lua.state, nargs, &mut nresults as *mut c_int); let ret = ffi::lua_resume(thread_state, lua.state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD { if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
protect_lua!(lua.state, 0, 0, |_| error_traceback(thread_state))?; check_stack(lua.state, 3)?;
return Err(pop_error(thread_state, ret)); protect_lua!(lua.state, 0, 1, |state| error_traceback_thread(
state,
thread_state
))?;
return Err(pop_error(lua.state, ret));
} }
let mut results = args; // Reuse MultiValue container let mut results = args; // Reuse MultiValue container
@@ -156,8 +159,7 @@ impl<'lua> Thread<'lua> {
pub fn status(&self) -> ThreadStatus { pub fn status(&self) -> ThreadStatus {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
let thread_state = let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let status = ffi::lua_status(thread_state); let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD { if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
@@ -199,8 +201,10 @@ impl<'lua> Thread<'lua> {
lua.push_ref(&self.0); lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1); let thread_state = ffi::lua_tothread(lua.state, -1);
#[cfg(feature = "lua54")] #[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state); 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")] #[cfg(feature = "lua54")]
if status != ffi::LUA_OK { if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status)); return Err(pop_error(thread_state, status));
@@ -322,7 +326,7 @@ impl<'lua> Thread<'lua> {
pub fn sandbox(&self) -> Result<()> { pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
let thread = lua.ref_thread_exec(|t| ffi::lua_tothread(t, self.0.index)); let thread = ffi::lua_tothread(lua.ref_thread(), self.0.index);
check_stack(thread, 1)?; check_stack(thread, 1)?;
check_stack(lua.state, 3)?; check_stack(lua.state, 3)?;
// Inherit `LUA_GLOBALSINDEX` from the caller // Inherit `LUA_GLOBALSINDEX` from the caller
@@ -357,7 +361,15 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> {
fn drop(&mut self) { fn drop(&mut self) {
if self.recycle { if self.recycle {
unsafe { unsafe {
self.thread.0.lua.recycle_thread(&mut self.thread); let lua = self.thread.0.lua;
// 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() == ThreadStatus::Error {
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.thread.0.index);
ffi::lua_resetthread(thread_state);
}
}
} }
} }
} }
+33 -4
View File
@@ -1,6 +1,7 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr}; use std::{fmt, mem, ptr};
@@ -104,6 +105,7 @@ pub(crate) struct DestructedUserdata;
/// [`AnyUserData::get_user_value`]: crate::AnyUserData::get_user_value /// [`AnyUserData::get_user_value`]: crate::AnyUserData::get_user_value
pub struct RegistryKey { pub struct RegistryKey {
pub(crate) registry_id: c_int, pub(crate) registry_id: c_int,
pub(crate) is_nil: AtomicBool,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>, pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
} }
@@ -129,15 +131,27 @@ impl Eq for RegistryKey {}
impl Drop for RegistryKey { impl Drop for RegistryKey {
fn drop(&mut self) { fn drop(&mut self) {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned"); // We don't need to collect nil slot
if let Some(list) = unref_list.as_mut() { if self.registry_id > ffi::LUA_REFNIL {
list.push(self.registry_id); let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
}
} }
} }
} }
impl RegistryKey { impl RegistryKey {
// Destroys the RegistryKey without adding to the drop list // Creates a new instance of `RegistryKey`
pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self {
RegistryKey {
registry_id: id,
is_nil: AtomicBool::new(id == ffi::LUA_REFNIL),
unref_list,
}
}
// Destroys the `RegistryKey` without adding to the unref list
pub(crate) fn take(self) -> c_int { pub(crate) fn take(self) -> c_int {
let registry_id = self.registry_id; let registry_id = self.registry_id;
unsafe { unsafe {
@@ -146,6 +160,21 @@ impl RegistryKey {
} }
registry_id registry_id
} }
// Returns true if this `RegistryKey` holds a nil value
#[inline(always)]
pub(crate) fn is_nil(&self) -> bool {
self.is_nil.load(Ordering::Relaxed)
}
// Marks value of this `RegistryKey` as `Nil`
#[inline(always)]
pub(crate) fn set_nil(&self, enabled: bool) {
// We cannot replace previous value with nil in as this will break
// Lua mechanism to find free keys.
// Instead, we set a special flag to mark value as nil.
self.is_nil.store(enabled, Ordering::Relaxed);
}
} }
pub(crate) struct LuaRef<'lua> { pub(crate) struct LuaRef<'lua> {
+31 -15
View File
@@ -242,15 +242,11 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
} }
} }
// Uses 3 stack spaces, does not call checkstack. // Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack.
#[inline] #[inline(always)]
pub unsafe fn push_string<S: AsRef<[u8]> + ?Sized>( pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
state: *mut ffi::lua_State, // Always use protected mode if the string is too long
s: &S, if protect || s.len() > (1 << 30) {
protect: bool,
) -> Result<()> {
let s = s.as_ref();
if protect {
protect_lua!(state, 0, 1, |state| { protect_lua!(state, 0, 1, |state| {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()); ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
}) })
@@ -313,7 +309,7 @@ pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool)
ptr::drop_in_place(ud as *mut T); ptr::drop_in_place(ud as *mut T);
} }
let size = mem::size_of::<T>() + 1; let size = mem::size_of::<T>();
let ud = if protect { let ud = if protect {
protect_lua!(state, 0, 1, |state| { protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
@@ -432,11 +428,17 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
} }
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
// Create and cache `__index` helper // Create and cache `__index` generator
let code = cstr!( let code = cstr!(
r#" r#"
local error, isfunction = ... local error, isfunction = ...
return function (__index, field_getters, methods) return function (__index, field_getters, methods)
-- Fastpath to return methods table for index access
if __index == nil and field_getters == nil then
return methods
end
-- Alternatively return a function for index access
return function (self, key) return function (self, key)
if field_getters ~= nil then if field_getters ~= nil then
local field_getter = field_getters[key] local field_getter = field_getters[key]
@@ -486,7 +488,7 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re
} }
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
// Create and cache `__newindex` helper // Create and cache `__newindex` generator
let code = cstr!( let code = cstr!(
r#" r#"
local error, isfunction = ... local error, isfunction = ...
@@ -548,7 +550,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__index` generator function // Push `__index` generator function
init_userdata_metatable_index(state)?; init_userdata_metatable_index(state)?;
push_string(state, "__index", true)?; push_string(state, b"__index", true)?;
let index_type = ffi::lua_rawget(state, -3); let index_type = ffi::lua_rawget(state, -3);
match index_type { match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -573,7 +575,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__newindex` generator function // Push `__newindex` generator function
init_userdata_metatable_newindex(state)?; init_userdata_metatable_newindex(state)?;
push_string(state, "__newindex", true)?; push_string(state, b"__newindex", true)?;
let newindex_type = ffi::lua_rawget(state, -3); let newindex_type = ffi::lua_rawget(state, -3);
match newindex_type { match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -696,6 +698,20 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
1 1
} }
// A variant of `error_traceback` that can safely inspect another (yielded) thread stack
pub unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ffi::lua_State) {
// Move error object to the main thread to safely call `__tostring` metamethod if present
ffi::lua_xmove(thread, state, 1);
if get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).is_null() {
let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, thread, s, 0);
ffi::lua_remove(state, -2);
}
}
}
// A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`. // A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`.
pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int { pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null()); ffi::luaL_checkstack(state, 2, ptr::null());
@@ -879,7 +895,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
} }
}?; }?;
push_string(state, &*err_buf, true)?; push_string(state, (*err_buf).as_bytes(), true)?;
(*err_buf).clear(); (*err_buf).clear();
Ok(1) Ok(1)
+9 -7
View File
@@ -111,11 +111,11 @@ impl<'lua> Value<'lua> {
Value::LightUserData(ud) => ud.0, Value::LightUserData(ud) => ud.0,
Value::Table(t) => t.to_pointer(), Value::Table(t) => t.to_pointer(),
Value::String(s) => s.to_pointer(), Value::String(s) => s.to_pointer(),
Value::Function(Function(v)) Value::Function(Function(r))
| Value::Thread(Thread(v)) | Value::Thread(Thread(r))
| Value::UserData(AnyUserData(v)) => v | Value::UserData(AnyUserData(r)) => {
.lua ffi::lua_topointer(r.lua.ref_thread(), r.index)
.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, v.index)), }
_ => ptr::null(), _ => ptr::null(),
} }
} }
@@ -197,7 +197,6 @@ pub struct MultiValue<'lua>(Vec<Value<'lua>>);
impl<'lua> MultiValue<'lua> { impl<'lua> MultiValue<'lua> {
/// Creates an empty `MultiValue` containing no values. /// Creates an empty `MultiValue` containing no values.
#[inline]
pub const fn new() -> MultiValue<'lua> { pub const fn new() -> MultiValue<'lua> {
MultiValue(Vec::new()) MultiValue(Vec::new())
} }
@@ -276,7 +275,10 @@ impl<'lua> MultiValue<'lua> {
#[inline] #[inline]
pub fn get(&self, index: usize) -> Option<&Value<'lua>> { pub fn get(&self, index: usize) -> Option<&Value<'lua>> {
self.0.get(self.0.len() - index - 1) if index < self.0.len() {
return self.0.get(self.0.len() - index - 1);
}
None
} }
#[inline] #[inline]
+8
View File
@@ -0,0 +1,8 @@
[lua54_coverage]
features = "lua54,vendored,async,serialize,macros"
[lua51_coverage]
features = "lua51,vendored,async,serialize,macros"
[luau_coverage]
features = "luau,async,serialize,macros"
+78
View File
@@ -174,6 +174,38 @@ async fn test_async_return_async_closure() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "lua54")]
#[tokio::test]
async fn test_async_lua54_to_be_closed() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("close_count", 0)?;
let code = r#"
local t <close> = setmetatable({}, {
__close = function()
close_count = close_count + 1
end
})
error "test"
"#;
let f = lua.load(code).into_function()?;
// Test close using call_async
let _ = f.call_async::<_, ()>(()).await;
assert_eq!(globals.get::<_, usize>("close_count")?, 1);
// Don't close by default when awaiting async threads
let co = lua.create_thread(f.clone())?;
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);
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_async_thread_stream() -> Result<()> { async fn test_async_thread_stream() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -278,6 +310,28 @@ async fn test_async_table() -> Result<()> {
Ok(()) Ok(())
} }
#[tokio::test]
async fn test_async_thread_cache() -> Result<()> {
let options = LuaOptions::new().thread_cache_size(4);
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let error_f = lua.create_async_function(|_, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Err::<(), _>(Error::RuntimeError("test".to_string()))
})?;
let sleep = lua.create_async_function(|_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
assert!(error_f.call_async::<_, ()>(()).await.is_err());
// Next call should use cached thread
assert_eq!(sleep.call_async::<_, String>(3).await?, "elapsed:3ms");
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_async_userdata() -> Result<()> { async fn test_async_userdata() -> Result<()> {
#[derive(Clone)] #[derive(Clone)]
@@ -372,6 +426,30 @@ async fn test_async_userdata() -> Result<()> {
Ok(()) Ok(())
} }
#[tokio::test]
async fn test_async_thread_error() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method("__tostring", |_, _this, ()| Ok("myuserdata error"))
}
}
let lua = Lua::new();
let result = lua
.load("function x(...) error(...) end x(...)")
.set_name("chunk")?
.call_async::<_, ()>(MyUserData)
.await;
assert!(
matches!(result, Err(Error::RuntimeError(cause)) if cause.contains("myuserdata error")),
"improper error traceback from dead thread"
);
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_async_scope() -> Result<()> { async fn test_async_scope() -> Result<()> {
let ref lua = Lua::new(); let ref lua = Lua::new();
+1 -3
View File
@@ -1,8 +1,6 @@
use mlua::{Lua, UserData, UserDataMethods}; use mlua::{UserData, UserDataMethods};
fn main() { fn main() {
let ref lua = Lua::new();
#[derive(Clone)] #[derive(Clone)]
struct MyUserData<'a>(&'a i64); struct MyUserData<'a>(&'a i64);
+14 -41
View File
@@ -1,41 +1,14 @@
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements error: lifetime may not live long enough
--> tests/compile/async_nonstatic_userdata.rs:11:72 --> tests/compile/async_nonstatic_userdata.rs:9:13
| |
11 | methods.add_async_method("print", |_, data, ()| async move { 7 | impl<'a> UserData for MyUserData<'a> {
| ________________________________________________________________________^ | -- lifetime `'a` defined here
12 | | println!("{}", data.0); 8 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
13 | | Ok(()) | ---- lifetime `'lua` defined here
14 | | }); 9 | / methods.add_async_method("print", |_, data, ()| async move {
| |_____________^ 10 | | println!("{}", data.0);
| 11 | | Ok(())
note: first, the lifetime cannot outlive the lifetime `'a` as defined here... 12 | | });
--> tests/compile/async_nonstatic_userdata.rs:9:10 | |______________^ argument requires that `'a` must outlive `'lua`
| |
9 | impl<'a> UserData for MyUserData<'a> { = help: consider adding the following bound: `'a: 'lua`
| ^^
note: ...so that the types are compatible
--> tests/compile/async_nonstatic_userdata.rs:11:72
|
11 | methods.add_async_method("print", |_, data, ()| async move {
| ________________________________________________________________________^
12 | | println!("{}", data.0);
13 | | Ok(())
14 | | });
| |_____________^
= note: expected `(MyUserData<'_>,)`
found `(MyUserData<'a>,)`
note: but, the lifetime must be valid for the lifetime `'lua` as defined here...
--> tests/compile/async_nonstatic_userdata.rs:10:24
|
10 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ^^^^
note: ...so that the type `impl Future<Output = [async output]>` will meet its required lifetime bounds...
--> tests/compile/async_nonstatic_userdata.rs:11:21
|
11 | methods.add_async_method("print", |_, data, ()| async move {
| ^^^^^^^^^^^^^^^^
note: ...that is required by this bound
--> src/userdata.rs
|
| MR: 'lua + Future<Output = Result<R>>;
| ^^^^
+4 -4
View File
@@ -1,10 +1,10 @@
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function error[E0373]: closure may outlive the current function, but it borrows `test.0`, which is owned by the current function
--> tests/compile/function_borrow.rs:9:33 --> tests/compile/function_borrow.rs:9:33
| |
9 | let _ = lua.create_function(|_, ()| -> Result<i32> { 9 | let _ = lua.create_function(|_, ()| -> Result<i32> {
| ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test` | ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test.0`
10 | Ok(test.0) 10 | Ok(test.0)
| ------ `test` is borrowed here | ------ `test.0` is borrowed here
| |
note: function requires argument type to outlive `'static` note: function requires argument type to outlive `'static`
--> tests/compile/function_borrow.rs:9:13 --> tests/compile/function_borrow.rs:9:13
@@ -14,7 +14,7 @@ note: function requires argument type to outlive `'static`
10 | | Ok(test.0) 10 | | Ok(test.0)
11 | | }); 11 | | });
| |______^ | |______^
help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword help: to force the closure to take ownership of `test.0` (and any other referenced variables), use the `move` keyword
| |
9 | let _ = lua.create_function(move |_, ()| -> Result<i32> { 9 | let _ = lua.create_function(move |_, ()| -> Result<i32> {
| ++++ | ++++
+5 -1
View File
@@ -10,7 +10,11 @@ error[E0277]: the type `UnsafeCell<mlua::lua::LuaInner>` may contain interior mu
= note: required because it appears within the type `Arc<UnsafeCell<mlua::lua::LuaInner>>` = note: required because it appears within the type `Arc<UnsafeCell<mlua::lua::LuaInner>>`
= note: required because it appears within the type `Lua` = note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua` = note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:48]` note: required because it's used within this closure
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: required by a bound in `catch_unwind` note: required by a bound in `catch_unwind`
--> $RUST/std/src/panic.rs --> $RUST/std/src/panic.rs
| |
+8 -1
View File
@@ -10,7 +10,14 @@ error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
| |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]` | |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
| |
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`, the trait `Send` is not implemented for `Rc<Cell<i32>>` = help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]` note: required because it's used within this closure
--> tests/compile/non_send.rs:11:25
|
11 | lua.create_function(move |_, ()| {
| _________________________^
12 | | Ok(data.get())
13 | | })?
| |_____^
= note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]` = note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
note: required by a bound in `Lua::create_function` note: required by a bound in `Lua::create_function`
--> src/lua.rs --> src/lua.rs
+5 -1
View File
@@ -12,7 +12,11 @@ error[E0277]: the type `UnsafeCell<mlua::lua::LuaInner>` may contain interior mu
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua` = note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>` = note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `LuaTable<'_>` = note: required because it appears within the type `LuaTable<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:54]` 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 `catch_unwind` note: required by a bound in `catch_unwind`
--> $RUST/std/src/panic.rs --> $RUST/std/src/panic.rs
| |
+4 -4
View File
@@ -1,13 +1,13 @@
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function
--> tests/compile/scope_invariance.rs:14:38 --> tests/compile/scope_invariance.rs:14:38
| |
9 | lua.scope(|scope| { 9 | lua.scope(|scope| {
| ----- has type `&mlua::Scope<'_, '1>` | ----- has type `&mlua::Scope<'_, '1>`
... ...
14 | .create_function_mut(|_, ()| { 14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test` | ^^^^^^^ may outlive borrowed value `test.field`
15 | test.field = 42; 15 | test.field = 42;
| ---------- `test` is borrowed here | ---------- `test.field` is borrowed here
| |
note: function requires argument type to outlive `'1` note: function requires argument type to outlive `'1`
--> tests/compile/scope_invariance.rs:13:13 --> tests/compile/scope_invariance.rs:13:13
@@ -19,7 +19,7 @@ note: function requires argument type to outlive `'1`
17 | | Ok(()) 17 | | Ok(())
18 | | })? 18 | | })?
| |__________________^ | |__________________^
help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword help: to force the closure to take ownership of `test.field` (and any other referenced variables), use the `move` keyword
| |
14 | .create_function_mut(move |_, ()| { 14 | .create_function_mut(move |_, ()| {
| ++++ | ++++
+22 -24
View File
@@ -1,33 +1,31 @@
error[E0597]: `lua` does not live long enough error[E0597]: `lua` does not live long enough
--> tests/compile/static_callback_args.rs:12:5 --> tests/compile/static_callback_args.rs:12:5
| |
12 | / lua.create_function(|_, table: Table| { 12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| { 13 | | BAD_TIME.with(|bt| {
14 | | *bt.borrow_mut() = Some(table); | |_________-
15 | | }); 14 | || *bt.borrow_mut() = Some(table);
16 | | Ok(()) 15 | || });
17 | | })? | ||__________- argument requires that `lua` is borrowed for `'static`
| | ^ 16 | | Ok(())
| | | 17 | | })?
| |______borrowed value does not live long enough | |______^ borrowed value does not live long enough
| argument requires that `lua` is borrowed for `'static`
... ...
32 | } 32 | }
| - `lua` dropped here while still borrowed | - `lua` dropped here while still borrowed
error[E0505]: cannot move out of `lua` because it is borrowed error[E0505]: cannot move out of `lua` because it is borrowed
--> tests/compile/static_callback_args.rs:22:10 --> tests/compile/static_callback_args.rs:22:10
| |
12 | / lua.create_function(|_, table: Table| { 12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| { 13 | | BAD_TIME.with(|bt| {
14 | | *bt.borrow_mut() = Some(table); | |_________-
15 | | }); 14 | || *bt.borrow_mut() = Some(table);
16 | | Ok(()) 15 | || });
17 | | })? | ||__________- argument requires that `lua` is borrowed for `'static`
| | - 16 | | Ok(())
| | | 17 | | })?
| |______borrow of `lua` occurs here | |______- borrow of `lua` occurs here
| argument requires that `lua` is borrowed for `'static`
... ...
22 | drop(lua); 22 | drop(lua);
| ^^^ move out of `lua` occurs here | ^^^ move out of `lua` occurs here
+26 -9
View File
@@ -1,7 +1,9 @@
#![cfg(feature = "luau")] #![cfg(feature = "luau")]
use std::env; use std::env;
use std::fmt::Debug;
use std::fs; use std::fs;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc; use std::sync::Arc;
@@ -75,18 +77,33 @@ fn test_vectors() -> Result<()> {
fn test_readonly_table() -> Result<()> { fn test_readonly_table() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let t = lua.create_table()?; let t = lua.create_sequence_from([1])?;
assert!(!t.is_readonly()); assert!(!t.is_readonly());
t.set_readonly(true); t.set_readonly(true);
assert!(t.is_readonly()); assert!(t.is_readonly());
match t.set("key", "value") { #[track_caller]
Err(Error::RuntimeError(err)) if err.contains("Attempt to modify a readonly table") => {} fn check_readonly_error<T: Debug>(res: Result<T>) {
r => panic!( match res {
"expected RuntimeError(...) with a specific message, got {:?}", Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
r r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
), }
}; }
check_readonly_error(t.set("key", "value"));
check_readonly_error(t.raw_set("key", "value"));
check_readonly_error(t.raw_insert(1, "value"));
check_readonly_error(t.raw_remove(1));
check_readonly_error(t.push("value"));
check_readonly_error(t.pop::<Value>());
check_readonly_error(t.raw_push("value"));
check_readonly_error(t.raw_pop::<Value>());
// Special case
match catch_unwind(AssertUnwindSafe(|| t.set_metatable(None))) {
Ok(_) => panic!("expected panic, got nothing"),
Err(_) => {}
}
Ok(()) Ok(())
} }
@@ -220,7 +237,7 @@ fn test_interrupts() -> Result<()> {
} }
#[test] #[test]
fn test_coverate() -> Result<()> { fn test_coverage() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
lua.set_compiler(Compiler::default().set_coverage_level(1)); lua.set_compiler(Compiler::default().set_coverage_level(1));
+18 -5
View File
@@ -391,31 +391,44 @@ fn test_from_value_newtype_struct() -> Result<(), Box<dyn std::error::Error>> {
#[test] #[test]
fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> { fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new(); let lua = Lua::new();
lua.globals().set("null", lua.null())?;
#[derive(Deserialize, PartialEq, Debug)] #[derive(Deserialize, PartialEq, Debug)]
enum E { struct UnitStruct;
#[derive(Deserialize, PartialEq, Debug)]
enum E<T = ()> {
Unit, Unit,
Integer(u32), Integer(u32),
Tuple(u32, u32), Tuple(u32, u32),
Struct { a: u32 }, Struct { a: u32 },
Wrap(T),
} }
let value = lua.load(r#""Unit""#).eval()?; let value = lua.load(r#""Unit""#).eval()?;
let got = lua.from_value(value)?; let got: E = lua.from_value(value)?;
assert_eq!(E::Unit, got); assert_eq!(E::Unit, got);
let value = lua.load(r#"{Integer = 1}"#).eval()?; let value = lua.load(r#"{Integer = 1}"#).eval()?;
let got = lua.from_value(value)?; let got: E = lua.from_value(value)?;
assert_eq!(E::Integer(1), got); assert_eq!(E::Integer(1), got);
let value = lua.load(r#"{Tuple = {1, 2}}"#).eval()?; let value = lua.load(r#"{Tuple = {1, 2}}"#).eval()?;
let got = lua.from_value(value)?; let got: E = lua.from_value(value)?;
assert_eq!(E::Tuple(1, 2), got); assert_eq!(E::Tuple(1, 2), got);
let value = lua.load(r#"{Struct = {a = 3}}"#).eval()?; let value = lua.load(r#"{Struct = {a = 3}}"#).eval()?;
let got = lua.from_value(value)?; let got: E = lua.from_value(value)?;
assert_eq!(E::Struct { a: 3 }, got); assert_eq!(E::Struct { a: 3 }, got);
let value = lua.load(r#"{Wrap = null}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Wrap(UnitStruct), got);
let value = lua.load(r#"{Wrap = null}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Wrap(()), got);
Ok(()) Ok(())
} }
+43
View File
@@ -109,6 +109,49 @@ fn test_table() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_table_push_pop() -> Result<()> {
let lua = Lua::new();
// Test raw access
let table1 = lua.create_sequence_from(vec![123])?;
table1.raw_push(321)?;
assert_eq!(
table1
.clone()
.raw_sequence_values::<i64>()
.collect::<Result<Vec<_>>>()?,
vec![123, 321]
);
assert_eq!(table1.raw_pop::<i64>()?, 321);
assert_eq!(table1.raw_pop::<i64>()?, 123);
assert_eq!(table1.raw_pop::<Value>()?, Value::Nil); // An extra pop should do nothing
assert_eq!(table1.raw_len(), 0);
// Test access through metamethods
let table2 = lua
.load(
r#"
local proxy_table = {234}
table2 = setmetatable({}, {
__len = function() return #proxy_table end,
__index = proxy_table,
__newindex = proxy_table,
})
return table2
"#,
)
.eval::<Table>()?;
table2.push(345)?;
assert_eq!(table2.len()?, 2);
assert_eq!(table2.pop::<i64>()?, 345);
assert_eq!(table2.pop::<i64>()?, 234);
assert_eq!(table2.pop::<Value>()?, Value::Nil);
assert_eq!(table2.len()?, 0);
Ok(())
}
#[test] #[test]
fn test_table_sequence_from() -> Result<()> { fn test_table_sequence_from() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
+40 -3
View File
@@ -69,10 +69,11 @@ fn test_safety() -> Result<()> {
fn test_load() -> Result<()> { fn test_load() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let func = lua.load("return 1+2").into_function()?; let func = lua.load("\treturn 1+2").into_function()?;
let result: i32 = func.call(())?; let result: i32 = func.call(())?;
assert_eq!(result, 3); assert_eq!(result, 3);
assert!(lua.load("").exec().is_ok());
assert!(lua.load("§$%§&$%&").exec().is_err()); assert!(lua.load("§$%§&$%&").exec().is_err());
Ok(()) Ok(())
@@ -275,7 +276,10 @@ fn test_error() -> Result<()> {
end, 3) end, 3)
local function handler(err) local function handler(err)
if string.match(_VERSION, ' 5%.1$') or string.match(_VERSION, ' 5%.2$') or _VERSION == "Luau" then if string.match(_VERSION, " 5%.1$")
or string.match(_VERSION, " 5%.2$")
or string.match(_VERSION, "Luau")
then
-- Special case for Lua 5.1/5.2 and Luau -- Special case for Lua 5.1/5.2 and Luau
local caps = string.match(err, ': (%d+)$') local caps = string.match(err, ': (%d+)$')
if caps then if caps then
@@ -792,6 +796,17 @@ fn test_replace_registry_value() -> Result<()> {
let key = lua.create_registry_value::<i32>(42)?; let key = lua.create_registry_value::<i32>(42)?;
lua.replace_registry_value(&key, "new value")?; lua.replace_registry_value(&key, "new value")?;
assert_eq!(lua.registry_value::<String>(&key)?, "new value"); assert_eq!(lua.registry_value::<String>(&key)?, "new value");
lua.replace_registry_value(&key, Value::Nil)?;
assert_eq!(lua.registry_value::<Value>(&key)?, Value::Nil);
lua.replace_registry_value(&key, 123)?;
assert_eq!(lua.registry_value::<i32>(&key)?, 123);
// It should be impossible to replace (initial) nil value with non-nil
let key2 = lua.create_registry_value(Value::Nil)?;
match lua.replace_registry_value(&key2, "abc") {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),
}
Ok(()) Ok(())
} }
@@ -843,6 +858,28 @@ fn test_mismatched_registry_key() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_registry_value_reuse() -> Result<()> {
let lua = Lua::new();
let r1 = lua.create_registry_value("value1")?;
let r1_slot = format!("{r1:?}");
drop(r1);
// Previous slot must not be reused by nil value
let r2 = lua.create_registry_value(Value::Nil)?;
let r2_slot = format!("{r2:?}");
assert_ne!(r1_slot, r2_slot);
drop(r2);
// But should be reused by non-nil value
let r3 = lua.create_registry_value("value3")?;
let r3_slot = format!("{r3:?}");
assert_eq!(r1_slot, r3_slot);
Ok(())
}
#[test] #[test]
fn test_application_data() -> Result<()> { fn test_application_data() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -954,7 +991,7 @@ fn test_ref_stack_exhaustion() {
match catch_unwind(AssertUnwindSafe(|| -> Result<()> { match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let mut vals = Vec::new(); let mut vals = Vec::new();
for _ in 0..1000000 { for _ in 0..10000000 {
vals.push(lua.create_table()?); vals.push(lua.create_table()?);
} }
Ok(()) Ok(())
+19 -1
View File
@@ -1,6 +1,6 @@
use std::ptr; use std::ptr;
use mlua::{Lua, Result, Value}; use mlua::{Lua, MultiValue, Result, Value};
#[test] #[test]
fn test_value_eq() -> Result<()> { fn test_value_eq() -> Result<()> {
@@ -63,3 +63,21 @@ fn test_value_eq() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_multi_value() {
let mut multi_value = MultiValue::new();
assert_eq!(multi_value.len(), 0);
assert_eq!(multi_value.get(0), None);
multi_value.push_front(Value::Number(2.));
multi_value.push_front(Value::Number(1.));
assert_eq!(multi_value.get(0), Some(&Value::Number(1.)));
assert_eq!(multi_value.get(1), Some(&Value::Number(2.)));
assert_eq!(multi_value.pop_front(), Some(Value::Number(1.)));
assert_eq!(multi_value[0], Value::Number(2.));
multi_value.clear();
assert!(multi_value.is_empty());
}