Compare commits

..

31 Commits

Author SHA1 Message Date
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
30 changed files with 669 additions and 365 deletions
+4 -4
View File
@@ -10,14 +10,14 @@ jobs:
options: --security-opt seccomp=unconfined
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Generate code coverage
- name: Generate coverage report
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
- name: Upload to codecov.io
uses: codecov/codecov-action@v1
- name: Upload report to codecov.io
uses: codecov/codecov-action@v3
with:
token: ${{secrets.CODECOV_TOKEN}}
fail_ci_if_error: false
+134 -143
View File
@@ -7,35 +7,34 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
include:
- os: ubuntu-20.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-20.04' && matrix.lua != 'lua54' }}
run: |
sudo apt-get update
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 }}"
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-22.04' }}
run: |
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
cargo build --features "${{ matrix.lua }}"
build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin
@@ -45,60 +44,57 @@ jobs:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: aarch64-apple-darwin
override: true
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: aarch64-apple-darwin
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: aarch64-unknown-linux-gnu
override: true
- name: Install ARM compiler toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: aarch64-unknown-linux-gnu
- name: Install ARM compiler toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
build_armv7_cross_ubuntu:
name: Cross-compile to armv7-unknown-linux-gnueabihf
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: armv7-unknown-linux-gnueabihf
override: true
- name: Install ARM compiler toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: armv7-unknown-linux-gnueabihf
- name: Install ARM compiler toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
test:
name: Test
@@ -106,35 +102,34 @@ jobs:
needs: build
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau]
include:
- os: ubuntu-20.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" -- --ignored
shell: bash
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
shell: bash
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" -- --ignored
shell: bash
test_with_sanitizer:
name: Test with address sanitizer
@@ -142,25 +137,24 @@ jobs:
needs: build
strategy:
matrix:
os: [ubuntu-20.04]
os: [ubuntu-22.04]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
include:
- os: ubuntu-20.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} tests with address sanitizer
run: |
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
shell: bash
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} tests with address sanitizer
run: |
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
shell: bash
test_modules:
name: Test modules
@@ -168,27 +162,26 @@ jobs:
needs: build
strategy:
matrix:
os: [ubuntu-20.04, macos-latest]
os: [ubuntu-22.04, macos-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-20.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} module tests
run: |
(cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }},vendored")
shell: bash
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} module tests
run: |
(cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }},vendored")
shell: bash
test_modules_windows:
name: Test modules on Windows
@@ -201,41 +194,39 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v2
- name: Install Rust & Lua
run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
- name: Run ${{ matrix.lua }} module tests
run: |
(cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }}")
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v3
- name: Install Rust & Lua
run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
- name: Run ${{ matrix.lua }} module tests
run: |
(cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }}")
rustfmt:
name: Rustfmt
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
override: true
- run: cargo fmt -- --check
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt
- run: cargo fmt -- --check
clippy:
name: Clippy check
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
components: clippy
override: true
toolchain: nightly
components: clippy
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
+19
View File
@@ -1,3 +1,22 @@
## 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
- Performance optimizations in handling UserData
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.8.2" # remember to update html_root_url and mlua_derive
version = "0.8.5" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2021"
repository = "https://github.com/khvzak/mlua"
@@ -58,7 +58,7 @@ cc = { version = "1.0" }
pkg-config = { version = "0.3.17" }
lua-src = { version = ">= 544.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true }
luau0-src = { version = "0.3.6", optional = true }
luau0-src = { version = "0.4.0", optional = true }
[dev-dependencies]
rustyline = "10.0"
+2 -2
View File
@@ -341,7 +341,7 @@ pub unsafe fn luaL_loadbufferx(
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'\n';
if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes();
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
} else {
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 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
}
+4 -1
View File
@@ -257,6 +257,7 @@ extern "C" {
extern "C" {
pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
// TODO: lua_encodepointer
pub fn lua_clock() -> c_double;
@@ -267,6 +268,7 @@ extern "C" {
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_cleartable(L: *mut lua_State, idx: c_int);
}
//
@@ -460,13 +462,14 @@ pub struct lua_Debug {
pub name: *const c_char,
pub what: *const c_char,
pub source: *const c_char,
pub short_src: *const c_char,
pub linedefined: c_int,
pub currentline: c_int,
pub nupvals: u8,
pub nparams: u8,
pub isvararg: c_char,
pub short_src: [c_char; LUA_IDSIZE],
pub userdata: *mut c_void,
pub ssbuf: [c_char; LUA_IDSIZE],
}
//
+3 -1
View File
@@ -79,9 +79,11 @@ pub(crate) fn keep_lua_symbols() {
symbols.push(lua_tocfunction as _);
symbols.push(luaL_loadstring 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_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)
}
/// 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.
///
/// Internally it wraps the function to an [`AsyncThread`].
@@ -273,10 +273,13 @@ impl<'lua> Function<'lua> {
name_what: None,
what: ptr_to_cstr_bytes(ar.what).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"))]
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);
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);
ffi::lua_dump(lua.state, writer, data_ptr, strip as i32);
ffi::lua_pop(lua.state, 1);
}
+7 -4
View File
@@ -102,10 +102,13 @@ impl<'lua> Debug<'lua> {
DebugSource {
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"))]
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),
}
}
@@ -125,7 +128,7 @@ impl<'lua> Debug<'lua> {
"lua_getinfo failed with `l`"
);
(*self.ar.get()).currentline as i32
(*self.ar.get()).currentline
}
}
+1 -1
View File
@@ -72,7 +72,7 @@
//! [`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")]
#![doc(html_root_url = "https://docs.rs/mlua/0.8.5")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
+65 -44
View File
@@ -402,6 +402,11 @@ impl Lua {
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?
let mut mem_diff = nsize as isize;
if !ptr.is_null() {
@@ -411,12 +416,14 @@ impl Lua {
if mem_info.memory_limit > 0 && new_used_memory > mem_info.memory_limit {
return ptr::null_mut();
}
let new_layout = Layout::from_size_align_unchecked(nsize, ffi::SYS_MIN_ALIGN);
mem_info.used_memory += mem_diff;
if ptr.is_null() {
// 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;
if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout);
@@ -428,7 +435,7 @@ impl Lua {
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;
if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout);
alloc::handle_alloc_error(old_layout);
}
new_ptr
}
@@ -437,12 +444,19 @@ impl Lua {
let use_rust_allocator = !(cfg!(feature = "luajit") && cfg!(not(feature = "vendored")));
let (state, mem_info) = if use_rust_allocator {
let mem_info = Box::into_raw(Box::new(MemoryInfo::default()));
let state = ffi::lua_newstate(allocator, mem_info as *mut c_void);
let mut mem_info = Box::into_raw(Box::new(MemoryInfo::default()));
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)
} else {
(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::lua_pop(state, 1);
@@ -496,6 +510,7 @@ impl Lua {
/// by calling this function again.
#[allow(clippy::missing_safety_doc)]
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_top = ffi::lua_gettop(main_state);
@@ -874,10 +889,8 @@ impl Lua {
ffi::luaL_sandboxthread(state);
} else {
// Restore original `LUA_GLOBALSINDEX`
self.ref_thread_exec(|ref_thread| {
ffi::lua_xpush(ref_thread, state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
});
ffi::lua_xpush(self.ref_thread(), state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
ffi::luaL_sandbox(state, 0);
}
})?;
@@ -1116,7 +1129,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
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()))?;
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(())
}
@@ -1358,9 +1371,8 @@ impl Lua {
where
S: AsChunk<'lua> + ?Sized,
{
let name = chunk
.name()
.unwrap_or_else(|| Location::caller().to_string());
let caller = Location::caller();
let name = chunk.name().unwrap_or_else(|| caller.to_string());
Chunk {
lua: self,
@@ -1420,11 +1432,15 @@ impl Lua {
S: AsRef<[u8]> + ?Sized,
{
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);
check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error();
push_string(self.state, s, protect)?;
push_string(self.state, s.as_ref(), true)?;
Ok(String(self.pop_ref()))
}
}
@@ -1440,11 +1456,15 @@ impl Lua {
/// 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> {
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);
check_stack(self.state, 3)?;
let protect = !self.unlikely_memory_error();
push_table(self.state, narr, nrec, protect)?;
push_table(self.state, narr, nrec, true)?;
Ok(Table(self.pop_ref()))
}
}
@@ -1712,15 +1732,16 @@ impl Lua {
all(feature = "luajit", feature = "vendored"),
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 thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
if extra.recycled_thread_cache.len() < extra.recycled_thread_cache.capacity() {
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
#[cfg(feature = "lua54")]
let status = ffi::lua_resetthread(thread_state);
#[cfg(feature = "lua54")]
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"))]
ffi::lua_resetthread(self.state, thread_state);
@@ -1728,7 +1749,9 @@ impl Lua {
ffi::lua_resetthread(thread_state);
extra.recycled_thread_cache.push(thread.0.index);
thread.0.index = 0;
return true;
}
false
}
/// Create a Lua userdata object from a custom userdata type.
@@ -2005,7 +2028,7 @@ impl Lua {
check_stack(self.state, 3)?;
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);
self.pop_value()
@@ -2083,11 +2106,8 @@ impl Lua {
let _sg = StackGuard::new(self.state);
check_stack(self.state, 1)?;
ffi::lua_rawgeti(
self.state,
ffi::LUA_REGISTRYINDEX,
key.registry_id as Integer,
);
let id = key.registry_id as Integer;
ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, id);
self.pop_value()
};
T::from_lua(value, self)
@@ -2133,14 +2153,10 @@ impl Lua {
self.push_value(t)?;
// It must be safe to replace the value without triggering memory error
ffi::lua_rawseti(
self.state,
ffi::LUA_REGISTRYINDEX,
key.registry_id as Integer,
);
Ok(())
let id = key.registry_id as Integer;
ffi::lua_rawseti(self.state, ffi::LUA_REGISTRYINDEX, id);
}
Ok(())
}
/// Returns true if the given `RegistryKey` was created by a `Lua` which shares the underlying
@@ -2252,7 +2268,7 @@ impl Lua {
}
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) => {
@@ -2428,6 +2444,13 @@ impl Lua {
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> {
unsafe {
let extra = &mut *self.extra.get();
@@ -2446,16 +2469,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<()> {
let extra = &mut *self.extra.get();
@@ -2580,6 +2593,7 @@ impl Lua {
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<Option<TypeId>> {
self.push_ref(lref);
if ffi::lua_getmetatable(self.state, -1) == 0 {
ffi::lua_pop(self.state, 1);
return Err(Error::UserDataTypeMismatch);
}
let mt_ptr = ffi::lua_topointer(self.state, -1);
@@ -2957,6 +2971,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);
impl<'a> StateGuard<'a> {
+1 -1
View File
@@ -71,7 +71,7 @@ impl Options {
///
/// [`deny_recursive_tables`]: #structfield.deny_recursive_tables
#[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
}
+3 -7
View File
@@ -10,7 +10,7 @@ use crate::ffi;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::LightUserData;
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::util::check_stack;
use crate::value::Value;
/// 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> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
push_array_metatable(self.state);
Table(self.pop_ref())
push_array_metatable(self.ref_thread());
Table(self.pop_ref_thread())
}
}
+8 -10
View File
@@ -13,7 +13,6 @@ use {
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{assert_stack, StackGuard};
/// Handle to an internal Lua string.
///
@@ -40,6 +39,7 @@ impl<'lua> String<'lua> {
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<&str> {
str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError {
from: "string",
@@ -66,6 +66,7 @@ impl<'lua> String<'lua> {
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn to_string_lossy(&self) -> Cow<'_, str> {
StdString::from_utf8_lossy(self.as_bytes())
}
@@ -87,6 +88,7 @@ impl<'lua> String<'lua> {
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let nulled = self.as_bytes_with_nul();
&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.
pub fn as_bytes_with_nul(&self) -> &[u8] {
let lua = self.0.lua;
let ref_thread = self.0.lua.ref_thread();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
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"
);
let mut size = 0;
// This will not trigger a 'm' error, because the reference is guaranteed to be of
// 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)
}
@@ -121,8 +119,8 @@ impl<'lua> String<'lua> {
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
let ref_thread = self.0.lua.ref_thread();
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
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 key = key.to_lua(lua)?;
let value = value.to_lua(lua)?;
@@ -98,6 +103,11 @@ impl<'lua> Table<'lua> {
///
/// [`raw_get`]: #method.raw_get
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 key = key.to_lua(lua)?;
@@ -116,18 +126,54 @@ impl<'lua> Table<'lua> {
/// Checks whether the table contains a non-nil value for `key`.
pub fn contains_key<K: ToLua<'lua>>(&self, key: K) -> Result<bool> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
Ok(self.get::<_, Value>(key)? != Value::Nil)
}
/// 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 {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
protect_lua!(lua.state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
Ok(ffi::lua_isnil(lua.state, -1) == 0)
lua.push_value(value)?;
protect_lua!(lua.state, 2, 0, fn(state) {
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.
@@ -188,6 +234,9 @@ impl<'lua> Table<'lua> {
/// Sets a key-value pair without invoking metamethods.
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 key = key.to_lua(lua)?;
let value = value.to_lua(lua)?;
@@ -199,6 +248,7 @@ impl<'lua> Table<'lua> {
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
if lua.unlikely_memory_error() {
ffi::lua_rawset(lua.state, -3);
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.
///
/// 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
pub fn len(&self) -> Result<Integer> {
// Fast track
if !self.has_metatable() {
return Ok(self.raw_len());
}
let lua = self.0.lua;
unsafe {
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.
pub fn raw_len(&self) -> Integer {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
ffi::lua_rawlen(lua.state, -1) as Integer
}
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_rawlen(ref_thread, self.0.index) as Integer }
}
/// 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
/// nothing).
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;
unsafe {
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.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_readonly(&self, enabled: bool) {
let lua = self.0.lua;
let ref_thread = self.0.lua.ref_thread();
unsafe {
lua.ref_thread_exec(|refthr| {
ffi::lua_setreadonly(refthr, self.0.index, enabled as _);
if !enabled {
// Reset "safeenv" flag
ffi::lua_setsafeenv(refthr, self.0.index, 0);
}
});
ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _);
if !enabled {
// Reset "safeenv" flag
ffi::lua_setsafeenv(ref_thread, self.0.index, 0);
}
}
}
@@ -379,8 +496,8 @@ impl<'lua> Table<'lua> {
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn is_readonly(&self) -> bool {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_getreadonly(refthr, self.0.index) != 0) }
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
}
/// 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.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua;
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) }
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
}
/// 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
}
}
#[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> {
+19 -9
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use crate::error::{Error, Result};
use crate::ffi;
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};
#[cfg(any(
@@ -118,8 +118,7 @@ impl<'lua> Thread<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, cmp::max(nargs + 1, 3))?;
let thread_state =
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
let status = ffi::lua_status(thread_state);
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);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
protect_lua!(lua.state, 0, 0, |_| error_traceback(thread_state))?;
return Err(pop_error(thread_state, ret));
check_stack(lua.state, 3)?;
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
@@ -156,8 +159,7 @@ impl<'lua> Thread<'lua> {
pub fn status(&self) -> ThreadStatus {
let lua = self.0.lua;
unsafe {
let thread_state =
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
@@ -322,7 +324,7 @@ impl<'lua> Thread<'lua> {
pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua;
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(lua.state, 3)?;
// Inherit `LUA_GLOBALSINDEX` from the caller
@@ -357,7 +359,15 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> {
fn drop(&mut self) {
if self.recycle {
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);
}
}
}
}
}
+21 -12
View File
@@ -242,14 +242,9 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
}
}
// Uses 3 stack spaces, does not call checkstack.
#[inline]
pub unsafe fn push_string<S: AsRef<[u8]> + ?Sized>(
state: *mut ffi::lua_State,
s: &S,
protect: bool,
) -> Result<()> {
let s = s.as_ref();
// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack.
#[inline(always)]
pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
@@ -313,7 +308,7 @@ pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool)
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 {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
@@ -548,7 +543,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__index` generator function
init_userdata_metatable_index(state)?;
push_string(state, "__index", true)?;
push_string(state, b"__index", true)?;
let index_type = ffi::lua_rawget(state, -3);
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -573,7 +568,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
push_string(state, "__newindex", true)?;
push_string(state, b"__newindex", true)?;
let newindex_type = ffi::lua_rawget(state, -3);
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -696,6 +691,20 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
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`.
pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
@@ -879,7 +888,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();
Ok(1)
+5 -5
View File
@@ -111,11 +111,11 @@ impl<'lua> Value<'lua> {
Value::LightUserData(ud) => ud.0,
Value::Table(t) => t.to_pointer(),
Value::String(s) => s.to_pointer(),
Value::Function(Function(v))
| Value::Thread(Thread(v))
| Value::UserData(AnyUserData(v)) => v
.lua
.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, v.index)),
Value::Function(Function(r))
| Value::Thread(Thread(r))
| Value::UserData(AnyUserData(r)) => {
ffi::lua_topointer(r.lua.ref_thread(), r.index)
}
_ => ptr::null(),
}
}
+78
View File
@@ -174,6 +174,38 @@ async fn test_async_return_async_closure() -> Result<()> {
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]
async fn test_async_thread_stream() -> Result<()> {
let lua = Lua::new();
@@ -278,6 +310,28 @@ async fn test_async_table() -> Result<()> {
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]
async fn test_async_userdata() -> Result<()> {
#[derive(Clone)]
@@ -372,6 +426,30 @@ async fn test_async_userdata() -> Result<()> {
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]
async fn test_async_scope() -> Result<()> {
let ref lua = Lua::new();
+1 -3
View File
@@ -1,8 +1,6 @@
use mlua::{Lua, UserData, UserDataMethods};
use mlua::{UserData, UserDataMethods};
fn main() {
let ref lua = Lua::new();
#[derive(Clone)]
struct MyUserData<'a>(&'a i64);
+14 -41
View File
@@ -1,41 +1,14 @@
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> 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: first, the lifetime cannot outlive the lifetime `'a` as defined here...
--> tests/compile/async_nonstatic_userdata.rs:9:10
|
9 | impl<'a> UserData for MyUserData<'a> {
| ^^
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>>;
| ^^^^
error: lifetime may not live long enough
--> tests/compile/async_nonstatic_userdata.rs:9:13
|
7 | impl<'a> UserData for MyUserData<'a> {
| -- lifetime `'a` defined here
8 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ---- lifetime `'lua` defined here
9 | / methods.add_async_method("print", |_, data, ()| async move {
10 | | println!("{}", data.0);
11 | | Ok(())
12 | | });
| |______________^ argument requires that `'a` must outlive `'lua`
|
= help: consider adding the following bound: `'a: 'lua`
+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
|
9 | let _ = lua.create_function(|_, ()| -> Result<i32> {
| ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test`
| ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test.0`
10 | Ok(test.0)
| ------ `test` is borrowed here
| ------ `test.0` is borrowed here
|
note: function requires argument type to outlive `'static`
--> tests/compile/function_borrow.rs:9:13
@@ -14,7 +14,7 @@ note: function requires argument type to outlive `'static`
10 | | Ok(test.0)
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> {
| ++++
+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 `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`
--> $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]`
|
= 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 by a bound in `Lua::create_function`
--> 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 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 `[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`
--> $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
|
9 | lua.scope(|scope| {
| ----- has type `&mlua::Scope<'_, '1>`
...
14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test`
| ^^^^^^^ may outlive borrowed value `test.field`
15 | test.field = 42;
| ---------- `test` is borrowed here
| ---------- `test.field` is borrowed here
|
note: function requires argument type to outlive `'1`
--> tests/compile/scope_invariance.rs:13:13
@@ -19,7 +19,7 @@ note: function requires argument type to outlive `'1`
17 | | Ok(())
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 |_, ()| {
| ++++
+22 -24
View File
@@ -1,33 +1,31 @@
error[E0597]: `lua` does not live long enough
--> tests/compile/static_callback_args.rs:12:5
|
12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| {
14 | | *bt.borrow_mut() = Some(table);
15 | | });
16 | | Ok(())
17 | | })?
| | ^
| | |
| |______borrowed value does not live long enough
| argument requires that `lua` is borrowed for `'static`
12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| {
| |_________-
14 | || *bt.borrow_mut() = Some(table);
15 | || });
| ||__________- argument requires that `lua` is borrowed for `'static`
16 | | Ok(())
17 | | })?
| |______^ borrowed value does not live long enough
...
32 | }
| - `lua` dropped here while still borrowed
32 | }
| - `lua` dropped here while still borrowed
error[E0505]: cannot move out of `lua` because it is borrowed
--> tests/compile/static_callback_args.rs:22:10
|
12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| {
14 | | *bt.borrow_mut() = Some(table);
15 | | });
16 | | Ok(())
17 | | })?
| | -
| | |
| |______borrow of `lua` occurs here
| argument requires that `lua` is borrowed for `'static`
12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| {
| |_________-
14 | || *bt.borrow_mut() = Some(table);
15 | || });
| ||__________- argument requires that `lua` is borrowed for `'static`
16 | | Ok(())
17 | | })?
| |______- borrow of `lua` occurs here
...
22 | drop(lua);
| ^^^ move out of `lua` occurs here
22 | drop(lua);
| ^^^ move out of `lua` occurs here
+26 -9
View File
@@ -1,7 +1,9 @@
#![cfg(feature = "luau")]
use std::env;
use std::fmt::Debug;
use std::fs;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
@@ -75,18 +77,33 @@ fn test_vectors() -> Result<()> {
fn test_readonly_table() -> Result<()> {
let lua = Lua::new();
let t = lua.create_table()?;
let t = lua.create_sequence_from([1])?;
assert!(!t.is_readonly());
t.set_readonly(true);
assert!(t.is_readonly());
match t.set("key", "value") {
Err(Error::RuntimeError(err)) if err.contains("Attempt to modify a readonly table") => {}
r => panic!(
"expected RuntimeError(...) with a specific message, got {:?}",
r
),
};
#[track_caller]
fn check_readonly_error<T: Debug>(res: Result<T>) {
match res {
Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
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(())
}
@@ -220,7 +237,7 @@ fn test_interrupts() -> Result<()> {
}
#[test]
fn test_coverate() -> Result<()> {
fn test_coverage() -> Result<()> {
let lua = Lua::new();
lua.set_compiler(Compiler::default().set_coverage_level(1));
+43
View File
@@ -109,6 +109,49 @@ fn test_table() -> Result<()> {
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]
fn test_table_sequence_from() -> Result<()> {
let lua = Lua::new();
+1
View File
@@ -73,6 +73,7 @@ fn test_load() -> Result<()> {
let result: i32 = func.call(())?;
assert_eq!(result, 3);
assert!(lua.load("").exec().is_ok());
assert!(lua.load("§$%§&$%&").exec().is_err());
Ok(())