Compare commits

..

62 Commits

Author SHA1 Message Date
Alex Orlenko 09af3e021a v0.6.6 2021-10-12 12:22:14 +01:00
Alex Orlenko 9e3d495f91 Calculate coverage for Lua 5.4 instead of Lua 5.3 2021-10-12 12:10:04 +01:00
Alex Orlenko cab2e5a48e Fix clippy warnings 2021-10-12 12:02:42 +01:00
Alex Orlenko 25a4879cde Update examples: async_http_client/async_http_server/async_tcp_server
Make them following best practices and remove `unsafe` code.
2021-10-12 11:55:25 +01:00
Alex Orlenko ed48b11e7f Update documentation references
Using rustdoc links (see RFC https://github.com/rust-lang/rfcs/pull/1946)
2021-10-12 00:49:45 +01:00
Alex Orlenko 559f9e6c6b Add target.aarch64-apple-darwin conf to module examples 2021-10-11 18:07:34 +01:00
Alex Orlenko 458b06796c Fixed LUA_REGISTRYINDEX when generating glue.rs on cross-compiling for lua51(jit) 2021-10-08 21:15:22 +01:00
Alex Orlenko 259eb09ae1 v0.6.5 2021-10-05 18:11:32 +01:00
Alex Orlenko a544e41b33 Add (hidden) method UserData::take() to take out value from userdata 2021-10-05 15:46:50 +01:00
Alex Orlenko 235fba821e Update CHANGELOG 2021-10-04 23:28:24 +01:00
Alex Orlenko c8c64a1b5a Add serializing i128/u128 types.
Fixes #81.
2021-10-04 23:20:11 +01:00
Alex Orlenko eff0bbb052 Add Location::caller() information to Lua::load() if chunk's name is None 2021-10-03 23:20:07 +01:00
Alex Orlenko d098c9ccf6 Refactor Waker handling in async code.
Instead of storing `Option<Waker>` in the Lua registry, store it on the reference thread.
It gives approx +10% performance gain when calling async function.
2021-10-03 22:09:19 +01:00
Alex Orlenko c62b17a5c8 Fixed bug when polling async futures (#77)
We expect first value returned via coroutine.yield() to be a special Pending type.
But instead we checked second value.
2021-10-02 07:23:24 +01:00
Alex Orlenko 584b377640 v0.6.4 2021-09-28 18:53:47 +01:00
Alex Orlenko 1141073a65 Update CHANGELOG 2021-09-28 18:51:42 +01:00
Alex Orlenko 5b1483bd56 Change syntax of protect_lua macro 2021-09-28 18:47:08 +01:00
Alex Orlenko a74b637ed4 Update check_stack requirements 2021-09-28 18:41:25 +01:00
Alex Orlenko 7623016d4a Fix doc feature labels 2021-09-28 18:15:53 +01:00
Alex Orlenko bdd3c923ba Fix table traversal used in recursion detection.
This fixes serializing same table multiple times within a parent table.
2021-09-28 16:41:39 +01:00
Alex Orlenko d586eef0f5 Refactor UserData metatables handling 2021-09-28 16:33:36 +01:00
Alex Orlenko 01154c0616 Minor refacor of wrapped failures cache 2021-09-28 16:27:24 +01:00
Alex Orlenko e42d67c70d Make protect_lua as a smart macro to choose from C/closure 2021-09-28 16:26:30 +01:00
Alex Orlenko 771a7775c5 Perf optimization: refactor metatable cache 2021-09-28 16:25:08 +01:00
Alex Orlenko ee1c8a1a3d Add inline attributes to few hot funcs 2021-09-28 16:19:29 +01:00
Alex Orlenko 3597e34ffb Fix internal HookCallback type (missing optional Send) 2021-09-28 16:19:12 +01:00
Alex Orlenko 35eedd5a5e v0.6.3 2021-09-16 00:55:49 +01:00
Alex Orlenko 53f873a482 Update compile tests error messages 2021-09-16 00:49:17 +01:00
Alex Orlenko fc1fe2c15e Add DeserializeOptions struct to control deserializer behavior.
This solves #74 and provides a way to deserialize a Lua globals table.
2021-09-15 23:45:08 +01:00
Alex Orlenko 7e7a44f4cd Update CHANGELOG 2021-08-22 16:31:14 +01:00
Alex Orlenko 63c4861520 Create FUNDING.yml 2021-08-22 16:22:20 +01:00
Alex Orlenko 27e7facf9b Fix clippy warnings 2021-08-22 00:35:31 +01:00
Alex Orlenko 31d32f2dda Wrap ExtraData to Arc<UnsafeCell>> instead of raw pointer and attach finalizer.
This would allow to properly deallocate memory in module mode when closing lua state.
2021-08-21 23:17:09 +01:00
Alex Orlenko 7d1b322e18 Change ExtraData::mem_info to Box<MemoryInfo> 2021-08-19 01:42:32 +01:00
Alex Orlenko d906405818 Simplify interface of hook::HookTriggers 2021-08-18 18:49:17 +01:00
Alex Orlenko 60fd060d47 Clarify about calling Lua::init_from_ptr() multiple times 2021-08-17 15:34:34 +01:00
Alex Orlenko 9f02a9ca09 Add Debug::event() to the hook's Debug structure 2021-08-17 15:17:03 +01:00
Alex Orlenko 1d7f105585 Don't catch Rust panics in userdata finalizer on drop 2021-08-06 11:14:16 +01:00
Alex Orlenko 1020315a9b Update documentation about FromLua for UserData. Closes #64 2021-08-04 12:12:02 +01:00
Alex Orlenko 628b70f561 v0.6.2 2021-07-13 20:29:25 +01:00
Alex Orlenko 8a7e546c66 Shrink unsafe block in Lua::load_from_function and update doc 2021-07-13 16:43:00 +01:00
Alex Orlenko 97bd288f56 Add CI tests with enabled address sanitizer
Don't use release mode in tests
Try to use cache in CI
2021-07-13 16:17:29 +01:00
Alex Orlenko 59253410bb Update trybuild messages 2021-07-13 11:59:33 +01:00
Alex Orlenko 5fb7b96704 Replace custom UserDataRef(Mut) with standard Ref(Mut) 2021-07-13 10:53:10 +01:00
Alex Orlenko 17162b1e0b UserData improvements:
1) Optimize `make_userdata` call
2) Refactor `UserDataWrapped` and make it enum
2021-07-11 01:30:17 +01:00
Alex Orlenko 934aa3b65f Forgotten optimization for box_method_mut 2021-07-09 23:25:48 +01:00
Alex Orlenko 3bffe1d7c3 Combine WrappedError and WrappedPanic structs to WrappedFailure enum 2021-07-09 00:05:29 +01:00
Alex Orlenko 582b2c585f Rename init_gc_metatable_for and get_gc_metatable_for 2021-07-08 22:57:54 +01:00
Alex Orlenko 56b6b128b5 Minor fixes/compilation after revert 2021-07-08 22:53:53 +01:00
Alex Orlenko 1731f5d61b Revert "Make protect_lua as a smart macro to choose from C/closure"
This reverts commit 84fe5f7f76.
2021-07-08 18:41:10 +01:00
Alex Orlenko 60822d12d2 Revert "Move away from metatable hashmap cache to direct keys"
This reverts commit adbc9ccc9b.
2021-07-08 18:40:14 +01:00
Alex Orlenko 8ff610529b Don't wrap ExtraData to Arc<Mutex> and use raw pointer instead.
This causes serious performance issues and given that Lua is
single threaded (not Sync) it's safe to use a raw pointer instead.
2021-07-08 17:32:20 +01:00
Alex Orlenko adbc9ccc9b Move away from metatable hashmap cache to direct keys 2021-07-08 15:37:18 +01:00
Alex Orlenko 84fe5f7f76 Make protect_lua as a smart macro to choose from C/closure 2021-07-07 12:54:19 +01:00
Alex Orlenko 1fe583027b Add new functions: lua.load_from_function() and lua.create_c_function()
This should be useful to register embedded C modules to Lua state.
Provides a solution for #61
2021-07-05 00:03:18 +01:00
Alex Orlenko 7b5b78fa3d Add codecov.yml to don't fail on coverage change 2021-06-30 21:52:50 +01:00
Alex Orlenko 41aae83304 Optimize callback creation
Attach only one upvalue to callbacks rather than two.
This leads to less lookup to Lua registry.
2021-06-30 16:50:50 +01:00
Alex Orlenko fc84e8623e v0.6.1 2021-06-28 00:10:07 +01:00
Alex Orlenko 8aae9a7c3e Update chunk! documentation 2021-06-28 00:10:04 +01:00
Alex Orlenko 24bbd62564 Fix converting Lua sequence table to HashSet/BTreeSet 2021-06-25 01:49:09 +01:00
Alex Orlenko 30af919931 Lower once_cell dependency to 1.0 2021-06-25 00:39:10 +01:00
Alex Orlenko 89b5981c59 Fix typos 2021-06-25 00:38:49 +01:00
45 changed files with 2207 additions and 1298 deletions
+1
View File
@@ -0,0 +1 @@
github: khvzak
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Generate code coverage
run: |
cargo tarpaulin --verbose --features lua53,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 --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
+40 -11
View File
@@ -24,17 +24,18 @@ jobs:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --release --features "${{ matrix.lua }},vendored"
cargo build --release --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-18.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 --release --features "${{ matrix.lua }}"
cargo build --features "${{ matrix.lua }}"
build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin
@@ -47,7 +48,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: stable
target: aarch64-apple-darwin
override: true
- name: Cross-compile
@@ -64,7 +65,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: stable
target: aarch64-unknown-linux-gnu
override: true
- name: Install ARM compiler toolchain
@@ -87,7 +88,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: stable
target: armv7-unknown-linux-gnueabihf
override: true
- name: Install ARM compiler toolchain
@@ -122,16 +123,43 @@ jobs:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --release --features "${{ matrix.lua }},vendored"
cargo test --release --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
shell: bash
- name: Run compile tests (macos lua53)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua53' }}
run: |
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }},vendored,async,send,serialize,macros" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" -- --ignored
shell: bash
test_with_sanitizer:
name: Test with address sanitizer
runs-on: ${{ matrix.os }}
needs: build
strategy:
matrix:
os: [ubuntu-18.04]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-18.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" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash
test_modules:
@@ -141,7 +169,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-18.04, macos-latest]
rust: [stable, nightly]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-18.04
@@ -155,6 +183,7 @@ jobs:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Run ${{ matrix.lua }} module tests
run: |
(cd examples/module && cargo build --release --features "${{ matrix.lua }},vendored")
+38 -2
View File
@@ -1,3 +1,39 @@
## v0.6.6
- Fixed calculating `LUA_REGISTRYINDEX` when cross-compiling for lua51/jit (#82)
- Updated documentation & examples
## v0.6.5
- Fixed bug when polling async futures (#77)
- Refactor Waker handling in async code (+10% performance gain when calling async functions)
- Added `Location::caller()` information to `Lua::load()` if chunk's name is None (Rust 1.46+)
- Added serialization of i128/u128 types (serde)
## v0.6.4
- Performance optimizations
- Fixed table traversal used in recursion detection in deserializer
## v0.6.3
- Disabled catching Rust panics in userdata finalizers on drop. It also has positive performance impact.
- Added `Debug::event()` to the hook's Debug structure
- Simplified interface of `hook::HookTriggers`
- Added finalizer to `ExtraData` in module mode. This helps avoiding memory leak on closing state when Lua unloads modules and frees memory.
- Added `DeserializeOptions` struct to control deserializer behavior (`from_value_with` function).
## v0.6.2
- New functionality: `Lua::load_from_function()` and `Lua::create_c_function()`
- Many optimizations in callbacks/userdata creation and methods execution
## v0.6.1
- Update `chunk!` documentation (stable Rust limitations)
- Fixed Lua sequence table conversion to HashSet/BTreeSet
- `once_cell` dependency lowered to 1.0
## v0.6.0
Changes since 0.5.4
- New `UserDataFields` API
@@ -7,7 +43,7 @@ Changes since 0.5.4
- Added `LuaOptions` to customize Lua/Rust behaviour (currently panic handling)
- Added `ToLua`/`FromLua` implementation for `Box<str>` and `Box<[T]>`.
- Added `Thread::reset()` for luajit/lua54 to recycle threads (coroutines) with attaching a new function.
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optinally capturing Rust variables.
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optionally capturing Rust variables.
- Improved errors reporting (`Error`'s `__tostring` method formats full stacktraces). This is useful in a module mode.
- Added `String::to_string_lossy`
- Various bugfixes and improvements
@@ -16,7 +52,7 @@ Breaking changes:
- Errors are always `Send + Sync` to be compatible with the anyhow crate.
- Removed `Result` from `LuaSerdeExt::null()` and `LuaSerdeExt::array_metatable()` (never fails)
- Removed `Result` from `Function::dump()` (never fails)
- Removed `AnyUserData::has_metamethod()` (if favour of full access to metatables)
- Removed `AnyUserData::has_metamethod()` (in favour of full access to metatables)
## v0.6.0-beta.3
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.6.0" # remember to update html_root_url and mlua_derive
version = "0.6.6" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
@@ -13,7 +13,7 @@ links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT)
with async/await features and support of writing native lua modules in Rust.
with async/await features and support of writing native Lua modules in Rust.
"""
[package.metadata.docs.rs]
@@ -43,7 +43,7 @@ macros = ["mlua_derive/macros"]
[dependencies]
mlua_derive = { version = "=0.6.0", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false }
once_cell = { version = "1.7" }
once_cell = { version = "1.0" }
num-traits = { version = "0.2.14" }
futures-core = { version = "0.3.5", optional = true }
futures-task = { version = "0.3.5", optional = true }
@@ -84,7 +84,7 @@ required-features = ["async", "serialize", "macros"]
[[example]]
name = "async_http_server"
required-features = ["async", "send"]
required-features = ["async", "macros"]
[[example]]
name = "async_tcp_server"
+17
View File
@@ -59,6 +59,22 @@ fn create_string_table(c: &mut Criterion) {
});
}
fn create_function(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [function] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for i in 0..10 {
lua.create_function(move |_, ()| Ok(i)).unwrap();
}
},
BatchSize::SmallInput,
);
});
}
fn call_lua_function(c: &mut Criterion) {
let lua = Lua::new();
@@ -258,6 +274,7 @@ criterion_group! {
create_table,
create_array,
create_string_table,
create_function,
call_lua_function,
call_sum_callback,
call_async_sum_callback,
+8 -6
View File
@@ -143,16 +143,18 @@ fn generate_glue() -> Result<()> {
(version.0 * 100) + version.1
)?;
let max_stack = if pointer_bit_width >= 32 {
1_000_000
} else {
15_000
};
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
writeln!(
glue,
"pub const LUA_REGISTRYINDEX: c_int = -{} - 1000;",
max_stack
if pointer_bit_width >= 32 {
1_000_000
} else {
15_000
}
)?;
#[cfg(any(feature = "lua51", feature = "luajit"))]
writeln!(glue, "pub const LUA_REGISTRYINDEX: c_int = -10000;")?;
// These two are only defined in lua 5.1
writeln!(glue, "pub const LUA_ENVIRONINDEX: c_int = -10001;")?;
+8
View File
@@ -0,0 +1,8 @@
coverage:
status:
patch:
default:
only_pulls: true
project:
default:
only_pulls: true
+7 -16
View File
@@ -1,26 +1,17 @@
use std::collections::HashMap;
use std::sync::Arc;
use hyper::body::{Body as HyperBody, HttpBody as _};
use hyper::Client as HyperClient;
use tokio::sync::Mutex;
use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods};
use mlua::{chunk, AnyUserData, ExternalResult, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct BodyReader(Arc<Mutex<HyperBody>>);
impl BodyReader {
fn new(body: HyperBody) -> Self {
BodyReader(Arc::new(Mutex::new(body)))
}
}
struct BodyReader(HyperBody);
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("read", |lua, reader, ()| async move {
let mut reader = reader.0.lock().await;
if let Some(bytes) = reader.data().await {
methods.add_async_function("read", |lua, reader: AnyUserData| async move {
let mut reader = reader.borrow_mut::<Self>()?;
if let Some(bytes) = reader.0.data().await {
let bytes = bytes.to_lua_err()?;
return Some(lua.create_string(&bytes)).transpose();
}
@@ -50,7 +41,7 @@ async fn main() -> Result<()> {
}
lua_resp.set("headers", headers)?;
lua_resp.set("body", BodyReader::new(resp.into_body()))?;
lua_resp.set("body", BodyReader(resp.into_body()))?;
Ok(lua_resp)
})?;
@@ -58,7 +49,7 @@ async fn main() -> Result<()> {
let f = lua
.load(chunk! {
local res = $fetch_url(...)
print(res.status)
print("status: "..res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
print(key..": "..val)
+90 -53
View File
@@ -1,10 +1,16 @@
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
use hyper::service::Service;
use hyper::{Body, Request, Response, Server};
use mlua::{Error, Function, Lua, Result, Table, UserData, UserDataMethods};
use mlua::{
chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods,
};
struct LuaRequest(SocketAddr, Request<Body>);
@@ -15,75 +21,106 @@ impl UserData for LuaRequest {
}
}
async fn run_server(handler: Function<'static>) -> Result<()> {
let make_svc = make_service_fn(|socket: &AddrStream| {
let remote_addr = socket.remote_addr();
let handler = handler.clone();
async move {
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
let handler = handler.clone();
async move {
let lua_req = LuaRequest(remote_addr, req);
let lua_resp: Table = handler.call_async(lua_req).await?;
let body = lua_resp
.get::<_, Option<String>>("body")?
.unwrap_or_default();
pub struct Svc(Rc<Lua>, SocketAddr);
let mut resp = Response::builder()
.status(lua_resp.get::<_, Option<u16>>("status")?.unwrap_or(200));
impl Service<Request<Body>> for Svc {
type Response = Response<Body>;
type Error = LuaError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
// If handler returns an error then generate 5xx response
let lua = self.0.clone();
let lua_req = LuaRequest(self.1, req);
Box::pin(async move {
let handler: Function = lua.named_registry_value("http_handler")?;
match handler.call_async::<_, Table>(lua_req).await {
Ok(lua_resp) => {
let status = lua_resp.get::<_, Option<u16>>("status")?.unwrap_or(200);
let mut resp = Response::builder().status(status);
// Set headers
if let Some(headers) = lua_resp.get::<_, Option<Table>>("headers")? {
for pair in headers.pairs::<String, String>() {
for pair in headers.pairs::<String, LuaString>() {
let (h, v) = pair?;
resp = resp.header(&h, v);
resp = resp.header(&h, v.as_bytes());
}
}
Ok::<_, Error>(resp.body(Body::from(body)).unwrap())
let body = lua_resp
.get::<_, Option<LuaString>>("body")?
.map(|b| Body::from(b.as_bytes().to_vec()))
.unwrap_or_else(Body::empty);
Ok(resp.body(body).unwrap())
}
}))
}
});
Err(err) => {
eprintln!("{}", err);
Ok(Response::builder()
.status(500)
.body(Body::from("Internal Server Error"))
.unwrap())
}
}
})
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let lua = Rc::new(Lua::new());
// Create Lua handler function
let handler: Function = lua
.load(chunk! {
function(req)
return {
status = 200,
headers = {
["X-Req-Method"] = req:method(),
["X-Remote-Addr"] = req:remote_addr(),
},
body = "Hello from Lua!\n"
}
end
})
.eval()
.expect("cannot create Lua handler");
// Store it in the Registry
lua.set_named_registry_value("http_handler", handler)
.expect("cannot store Lua handler");
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr).executor(LocalExec).serve(make_svc);
let server = Server::bind(&addr).executor(LocalExec).serve(MakeSvc(lua));
println!("Listening on http://{}", addr);
tokio::task::LocalSet::new()
.run_until(server)
.await
.map_err(Error::external)
// Create `LocalSet` to spawn !Send futures
let local = tokio::task::LocalSet::new();
local.run_until(server).await.expect("cannot run server")
}
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new().into_static();
struct MakeSvc(Rc<Lua>);
let handler: Function = lua
.load(
r#"
function(req)
return {
status = 200,
headers = {
["X-Req-Method"] = req:method(),
["X-Remote-Addr"] = req:remote_addr(),
},
body = "Hello, World!\n"
}
end
"#,
)
.eval()?;
impl Service<&AddrStream> for MakeSvc {
type Response = Svc;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
run_server(handler).await?;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
// Consume the static reference and drop it.
// This is safe as long as we don't hold any other references to Lua
// or alive resources.
unsafe { Lua::from_static(lua) };
Ok(())
fn call(&mut self, stream: &AddrStream) -> Self::Future {
let lua = self.0.clone();
let remote_addr = stream.remote_addr();
Box::pin(async move { Ok(Svc(lua, remote_addr)) })
}
}
#[derive(Clone, Copy, Debug)]
+88 -89
View File
@@ -1,122 +1,121 @@
use std::sync::Arc;
use std::io;
use std::net::SocketAddr;
use std::rc::Rc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::Mutex;
use tokio::task;
use mlua::{chunk, Function, Lua, Result, String as LuaString, UserData, UserDataMethods};
use mlua::{
chunk, AnyUserData, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods,
};
struct LuaTcp;
#[derive(Clone)]
struct LuaTcpListener(Arc<Mutex<TcpListener>>);
#[derive(Clone)]
struct LuaTcpStream(Arc<Mutex<TcpStream>>);
impl UserData for LuaTcp {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("bind", |_, addr: String| async move {
let listener = TcpListener::bind(addr).await?;
Ok(LuaTcpListener(Arc::new(Mutex::new(listener))))
});
}
}
impl UserData for LuaTcpListener {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("accept", |_, listener, ()| async move {
let (stream, _) = listener.0.lock().await.accept().await?;
Ok(LuaTcpStream(Arc::new(Mutex::new(stream))))
});
}
}
struct LuaTcpStream(TcpStream);
impl UserData for LuaTcpStream {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("peer_addr", |_, stream, ()| async move {
Ok(stream.0.lock().await.peer_addr()?.to_string())
methods.add_method("peer_addr", |_, this, ()| {
Ok(this.0.peer_addr()?.to_string())
});
methods.add_async_method("read", |lua, stream, size: usize| async move {
let mut buf = vec![0; size];
let n = stream.0.lock().await.read(&mut buf).await?;
buf.truncate(n);
lua.create_string(&buf)
});
methods.add_async_function(
"read",
|lua, (this, size): (AnyUserData, usize)| async move {
let mut this = this.borrow_mut::<Self>()?;
let mut buf = vec![0; size];
let n = this.0.read(&mut buf).await?;
buf.truncate(n);
lua.create_string(&buf)
},
);
methods.add_async_method("write", |_, stream, data: LuaString| async move {
let n = stream.0.lock().await.write(&data.as_bytes()).await?;
Ok(n)
});
methods.add_async_function(
"write",
|_, (this, data): (AnyUserData, LuaString)| async move {
let mut this = this.borrow_mut::<Self>()?;
let n = this.0.write(&data.as_bytes()).await?;
Ok(n)
},
);
methods.add_async_method("close", |_, stream, ()| async move {
stream.0.lock().await.shutdown().await?;
methods.add_async_function("close", |_, this: AnyUserData| async move {
let mut this = this.borrow_mut::<Self>()?;
this.0.shutdown().await?;
Ok(())
});
}
}
async fn run_server(lua: &'static Lua) -> Result<()> {
let spawn = lua.create_function(move |_, func: Function| {
task::spawn_local(async move { func.call_async::<_, ()>(()).await });
Ok(())
})?;
async fn run_server(lua: Lua, handler: RegistryKey) -> io::Result<()> {
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
let listener = TcpListener::bind(addr).await.expect("cannot bind addr");
let tcp = LuaTcp;
println!("Listening on {}", addr);
let server = lua
let lua = Rc::new(lua);
let handler = Rc::new(handler);
loop {
let (stream, _) = match listener.accept().await {
Ok(res) => res,
Err(err) if is_transient_error(&err) => continue,
Err(err) => return Err(err),
};
let lua = lua.clone();
let handler = handler.clone();
task::spawn_local(async move {
let handler: Function = lua
.registry_value(&handler)
.expect("cannot get Lua handler");
let stream = LuaTcpStream(stream);
if let Err(err) = handler.call_async::<_, ()>(stream).await {
eprintln!("{}", err);
}
});
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let lua = Lua::new();
// Create Lua handler function
let handler_fn = lua
.load(chunk! {
local addr = ...
local listener = $tcp.bind(addr)
print("listening on "..addr)
local accept_new = true
while true do
local stream = listener:accept()
function(stream)
local peer_addr = stream:peer_addr()
print("connected from "..peer_addr)
if not accept_new then
return
end
$spawn(function()
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") -- trim
print("["..peer_addr.."] "..data)
if data == "bye" then
stream:write("bye bye\n")
stream:close()
return
end
if data == "exit" then
stream:close()
accept_new = false
return
end
stream:write("echo: "..data.."\n")
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") // trim
print("["..peer_addr.."] "..data)
if data == "bye" then
stream:write("bye bye\n")
stream:close()
return
end
end)
stream:write("echo: "..data.."\n")
end
end
})
.into_function()?;
.eval::<Function>()
.expect("cannot create Lua handler");
// Store it in the Registry
let handler = lua
.create_registry_value(handler_fn)
.expect("cannot store Lua handler");
task::LocalSet::new()
.run_until(server.call_async::<_, ()>("0.0.0.0:1234"))
.run_until(run_server(lua, handler))
.await
.expect("cannot run server")
}
#[tokio::main]
async fn main() {
let lua = Lua::new().into_static();
run_server(lua).await.unwrap();
// Consume the static reference and drop it.
// This is safe as long as we don't hold any other references to Lua
// or alive resources.
unsafe { Lua::from_static(lua) };
fn is_transient_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}
+6
View File
@@ -3,3 +3,9 @@ rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
[target.aarch64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
+38 -12
View File
@@ -20,24 +20,28 @@ use crate::userdata::{AnyUserData, UserData};
use crate::value::{FromLua, Nil, ToLua, Value};
impl<'lua> ToLua<'lua> for Value<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(self)
}
}
impl<'lua> FromLua<'lua> for Value<'lua> {
#[inline]
fn from_lua(lua_value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
Ok(lua_value)
}
}
impl<'lua> ToLua<'lua> for String<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(self))
}
}
impl<'lua> FromLua<'lua> for String<'lua> {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<String<'lua>> {
let ty = value.type_name();
lua.coerce_string(value)?
@@ -50,12 +54,14 @@ impl<'lua> FromLua<'lua> for String<'lua> {
}
impl<'lua> ToLua<'lua> for Table<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(self))
}
}
impl<'lua> FromLua<'lua> for Table<'lua> {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Table<'lua>> {
match value {
Value::Table(table) => Ok(table),
@@ -69,12 +75,14 @@ impl<'lua> FromLua<'lua> for Table<'lua> {
}
impl<'lua> ToLua<'lua> for Function<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Function(self))
}
}
impl<'lua> FromLua<'lua> for Function<'lua> {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Function<'lua>> {
match value {
Value::Function(table) => Ok(table),
@@ -88,12 +96,14 @@ impl<'lua> FromLua<'lua> for Function<'lua> {
}
impl<'lua> ToLua<'lua> for Thread<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Thread(self))
}
}
impl<'lua> FromLua<'lua> for Thread<'lua> {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Thread<'lua>> {
match value {
Value::Thread(t) => Ok(t),
@@ -107,12 +117,14 @@ impl<'lua> FromLua<'lua> for Thread<'lua> {
}
impl<'lua> ToLua<'lua> for AnyUserData<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(self))
}
}
impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<AnyUserData<'lua>> {
match value {
Value::UserData(ud) => Ok(ud),
@@ -126,12 +138,14 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
}
impl<'lua, T: 'static + MaybeSend + UserData> ToLua<'lua> for T {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(lua.create_userdata(self)?))
}
}
impl<'lua, T: 'static + UserData + Clone> FromLua<'lua> for T {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<T> {
match value {
Value::UserData(ud) => Ok(ud.borrow::<T>()?.clone()),
@@ -145,12 +159,14 @@ impl<'lua, T: 'static + UserData + Clone> FromLua<'lua> for T {
}
impl<'lua> ToLua<'lua> for Error {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Error(self))
}
}
impl<'lua> FromLua<'lua> for Error {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Error> {
match value {
Value::Error(err) => Ok(err),
@@ -164,12 +180,14 @@ impl<'lua> FromLua<'lua> for Error {
}
impl<'lua> ToLua<'lua> for bool {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Boolean(self))
}
}
impl<'lua> FromLua<'lua> for bool {
#[inline]
fn from_lua(v: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match v {
Value::Nil => Ok(false),
@@ -180,12 +198,14 @@ impl<'lua> FromLua<'lua> for bool {
}
impl<'lua> ToLua<'lua> for LightUserData {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::LightUserData(self))
}
}
impl<'lua> FromLua<'lua> for LightUserData {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::LightUserData(ud) => Ok(ud),
@@ -199,12 +219,14 @@ impl<'lua> FromLua<'lua> for LightUserData {
}
impl<'lua> ToLua<'lua> for StdString {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?))
}
}
impl<'lua> FromLua<'lua> for StdString {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self> {
let ty = value.type_name();
Ok(lua
@@ -220,6 +242,7 @@ impl<'lua> FromLua<'lua> for StdString {
}
impl<'lua> ToLua<'lua> for &str {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self)?))
}
@@ -328,6 +351,7 @@ macro_rules! lua_convert_int {
if let Some(i) = cast(self) {
Ok(Value::Integer(i))
} else {
// TODO: Remove conversion to Number in v0.7
cast(self)
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x),
@@ -554,17 +578,17 @@ impl<'lua, T: Eq + Hash + ToLua<'lua>, S: BuildHasher> ToLua<'lua> for HashSet<T
impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> for HashSet<T, S> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
match value {
Value::Table(table) if table.len()? > 0 => table.sequence_values().collect(),
Value::Table(table) => table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
.collect(),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "HashSet",
message: Some("expected table".to_string()),
})
}),
}
}
}
@@ -579,22 +603,23 @@ impl<'lua, T: Ord + ToLua<'lua>> ToLua<'lua> for BTreeSet<T> {
impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
match value {
Value::Table(table) if table.len()? > 0 => table.sequence_values().collect(),
Value::Table(table) => table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
.collect(),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "BTreeSet",
message: Some("expected table".to_string()),
})
}),
}
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Option<T> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
match self {
Some(val) => val.to_lua(lua),
@@ -604,6 +629,7 @@ impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Option<T> {
}
impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option<T> {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self> {
match value {
Nil => Ok(None),
+14 -12
View File
@@ -96,8 +96,8 @@ pub enum Error {
/// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this
/// error.
///
/// [`Thread::resume`]: struct.Thread.html#method.resume
/// [`Thread::status`]: struct.Thread.html#method.status
/// [`Thread::resume`]: crate::Thread::resume
/// [`Thread::status`]: crate::Thread::status
CoroutineInactive,
/// An [`AnyUserData`] is not the expected type in a borrow.
///
@@ -105,15 +105,15 @@ pub enum Error {
/// metamethods for binary operators. Refer to the documentation of [`UserDataMethods`] for
/// details.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`UserDataMethods`]: crate::UserDataMethods
UserDataTypeMismatch,
/// An [`AnyUserData`] borrow failed because it has been destructed.
///
/// This error can happen either due to to being destructed in a previous __gc, or due to being
/// destructed from exiting a `Lua::scope` call.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
UserDataDestructed,
/// An [`AnyUserData`] immutable borrow failed because it is already borrowed mutably.
///
@@ -121,8 +121,8 @@ pub enum Error {
/// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
/// prevent these errors.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserData`]: trait.UserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`UserData`]: crate::UserData
UserDataBorrowError,
/// An [`AnyUserData`] mutable borrow failed because it is already borrowed.
///
@@ -130,22 +130,24 @@ pub enum Error {
/// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
/// prevent these errors.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserData`]: trait.UserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`UserData`]: crate::UserData
UserDataBorrowMutError,
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
///
/// [`MetaMethod`]: enum.MetaMethod.html
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodRestricted(StdString),
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
///
/// [`MetaMethod`]: enum.MetaMethod.html
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodTypeError {
method: StdString,
type_name: &'static str,
message: Option<StdString>,
},
/// A `RegistryKey` produced from a different Lua state was used.
/// A [`RegistryKey`] produced from a different Lua state was used.
///
/// [`RegistryKey`]: crate::RegistryKey
MismatchedRegistryKey,
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
CallbackError {
+3 -2
View File
@@ -80,6 +80,7 @@ pub const LUA_ERRERR: c_int = 5;
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub const LUA_ERRERR: c_int = 6;
/// A raw Lua state associated with a thread.
pub type lua_State = c_void;
// basic types
@@ -121,14 +122,14 @@ pub type lua_Number = luaconf::LUA_NUMBER;
/// A Lua integer, usually equivalent to `i64`.
pub type lua_Integer = luaconf::LUA_INTEGER;
// unsigned integer type
/// A Lua unsigned integer, usually equivalent to `u64`.
pub type lua_Unsigned = luaconf::LUA_UNSIGNED;
// type for continuation-function contexts
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub type lua_KContext = luaconf::LUA_KCONTEXT;
/// Type for native functions that can be passed to Lua.
/// Type for native C functions that can be passed to Lua.
pub type lua_CFunction = unsafe extern "C" fn(L: *mut lua_State) -> c_int;
// Type for continuation functions
+4 -4
View File
@@ -5,7 +5,7 @@ use std::slice;
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{assert_stack, check_stack, error_traceback, pop_error, protect_lua, StackGuard};
use crate::util::{assert_stack, check_stack, error_traceback, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(feature = "async")]
@@ -116,7 +116,7 @@ impl<'lua> Function<'lua> {
/// # }
/// ```
///
/// [`AsyncThread`]: struct.AsyncThread.html
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
@@ -198,8 +198,8 @@ impl<'lua> Function<'lua> {
for arg in args {
lua.push_value(arg)?;
}
protect_lua(lua.state, nargs + 2, 1, |state| {
ffi::lua_pushcclosure(state, bind_call_impl, nargs + 2);
protect_lua!(lua.state, nargs + 2, 1, fn(state) {
ffi::lua_pushcclosure(state, bind_call_impl, ffi::lua_gettop(state));
})?;
Ok(Function(lua.pop_ref()))
+91 -1
View File
@@ -1,5 +1,6 @@
use std::ffi::CStr;
use std::marker::PhantomData;
use std::ops::{BitOr, BitOrAssign};
use std::os::raw::{c_char, c_int};
use crate::ffi::{self, lua_Debug, lua_State};
@@ -14,7 +15,7 @@ use crate::util::callback_error;
/// found in the [Lua 5.3 documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.3/manual.html#lua_Debug
/// [`Lua::set_hook`]: struct.Lua.html#method.set_hook
/// [`Lua::set_hook`]: crate::Lua::set_hook
#[derive(Clone)]
pub struct Debug<'a> {
ar: *mut lua_Debug,
@@ -23,6 +24,25 @@ pub struct Debug<'a> {
}
impl<'a> Debug<'a> {
/// Returns the specific event that triggered the hook.
///
/// For [Lua 5.1] `DebugEvent::TailCall` is used for return events to indicate a return
/// from a function that did a tail call.
///
/// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET
pub fn event(&self) -> DebugEvent {
unsafe {
match (*self.ar).event {
ffi::LUA_HOOKCALL => DebugEvent::Call,
ffi::LUA_HOOKRET => DebugEvent::Ret,
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
ffi::LUA_HOOKLINE => DebugEvent::Line,
ffi::LUA_HOOKCOUNT => DebugEvent::Count,
event => mlua_panic!("Unknown Lua event code: {}", event),
}
}
}
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames<'a> {
unsafe {
@@ -95,6 +115,16 @@ impl<'a> Debug<'a> {
}
}
/// Represents a specific event that triggered the hook.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugEvent {
Call,
Ret,
TailCall,
Line,
Count,
}
#[derive(Clone, Debug)]
pub struct DebugNames<'a> {
pub name: Option<&'a [u8]>,
@@ -140,6 +170,46 @@ pub struct HookTriggers {
}
impl HookTriggers {
/// Returns a new instance of `HookTriggers` with [`on_calls`] trigger set.
///
/// [`on_calls`]: #structfield.on_calls
pub fn on_calls() -> Self {
HookTriggers {
on_calls: true,
..Default::default()
}
}
/// Returns a new instance of `HookTriggers` with [`on_returns`] trigger set.
///
/// [`on_returns`]: #structfield.on_returns
pub fn on_returns() -> Self {
HookTriggers {
on_returns: true,
..Default::default()
}
}
/// Returns a new instance of `HookTriggers` with [`every_line`] trigger set.
///
/// [`every_line`]: #structfield.every_line
pub fn every_line() -> Self {
HookTriggers {
every_line: true,
..Default::default()
}
}
/// Returns a new instance of `HookTriggers` with [`every_nth_instruction`] trigger set.
///
/// [`every_nth_instruction`]: #structfield.every_nth_instruction
pub fn every_nth_instruction(n: u32) -> Self {
HookTriggers {
every_nth_instruction: Some(n),
..Default::default()
}
}
// Compute the mask to pass to `lua_sethook`.
pub(crate) fn mask(&self) -> c_int {
let mut mask: c_int = 0;
@@ -165,6 +235,26 @@ impl HookTriggers {
}
}
impl BitOr for HookTriggers {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
self.on_calls |= rhs.on_calls;
self.on_returns |= rhs.on_returns;
self.every_line |= rhs.every_line;
if self.every_nth_instruction.is_none() && rhs.every_nth_instruction.is_some() {
self.every_nth_instruction = rhs.every_nth_instruction;
}
self
}
}
impl BitOrAssign for HookTriggers {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
pub(crate) unsafe extern "C" fn hook_proc(state: *mut lua_State, ar: *mut lua_Debug) {
callback_error(state, |_| {
let debug = Debug {
+40 -31
View File
@@ -50,29 +50,29 @@
//! to [`Function`]s and [`UserData`].
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: struct.Lua.html
//! [executing]: struct.Chunk.html#method.exec
//! [evaluating]: struct.Chunk.html#method.eval
//! [globals]: struct.Lua.html#method.globals
//! [`ToLua`]: trait.ToLua.html
//! [`FromLua`]: trait.FromLua.html
//! [`ToLuaMulti`]: trait.ToLuaMulti.html
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
//! [`Function`]: struct.Function.html
//! [`UserData`]: trait.UserData.html
//! [`UserDataFields`]: trait.UserDataFields.html
//! [`UserDataMethods`]: trait.UserDataMethods.html
//! [`LuaSerdeExt`]: serde/trait.LuaSerdeExt.html
//! [`Value`]: enum.Value.html
//! [`create_async_function`]: struct.Lua.html#method.create_async_function
//! [`call_async`]: struct.Function.html#method.call_async
//! [`AsyncThread`]: struct.AsyncThread.html
//! [`Future`]: ../futures_core/future/trait.Future.html
//! [`Lua`]: crate::Lua
//! [executing]: crate::Chunk::exec
//! [evaluating]: crate::Chunk::eval
//! [globals]: crate::Lua::globals
//! [`ToLua`]: crate::ToLua
//! [`FromLua`]: crate::FromLua
//! [`ToLuaMulti`]: crate::ToLuaMulti
//! [`FromLuaMulti`]: crate::FromLuaMulti
//! [`Function`]: crate::Function
//! [`UserData`]: crate::UserData
//! [`UserDataFields`]: crate::UserDataFields
//! [`UserDataMethods`]: crate::UserDataMethods
//! [`LuaSerdeExt`]: crate::LuaSerdeExt
//! [`Value`]: crate::Value
//! [`create_async_function`]: crate::Lua::create_async_function
//! [`call_async`]: crate::Function::call_async
//! [`AsyncThread`]: crate::AsyncThread
//! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.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.6.0")]
#![doc(html_root_url = "https://docs.rs/mlua/0.6.6")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
@@ -98,12 +98,13 @@ mod userdata;
mod util;
mod value;
#[doc(hidden)]
pub use crate::ffi::lua_State;
pub mod prelude;
pub use crate::{ffi::lua_CFunction, ffi::lua_State};
pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers};
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack, HookTriggers};
pub use crate::lua::{AsChunk, Chunk, ChunkMode, GCMode, Lua, LuaOptions};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
@@ -122,9 +123,10 @@ pub use crate::thread::AsyncThread;
#[cfg(feature = "serialize")]
#[doc(inline)]
pub use crate::serde::{ser::Options as SerializeOptions, LuaSerdeExt};
pub use crate::serde::{
de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt,
};
pub mod prelude;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
@@ -139,9 +141,9 @@ extern crate mlua_derive;
/// This macro allows to write Lua code directly in Rust code.
///
/// Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
/// User Rust types needs to implement [`UserData`] or [`ToLua`] traits.
/// User's Rust types needs to implement [`UserData`] or [`ToLua`] traits.
///
/// Captured variables are moved into the chunk.
/// Captured variables are **moved** into the chunk.
///
/// ```
/// use mlua::{Lua, Result, chunk};
@@ -165,20 +167,27 @@ extern crate mlua_derive;
/// (Single quoted strings only work if they contain a single character, since in Rust,
/// `'a'` is a character literal).
///
/// - Using Lua comments `--` is not desirable in **stable** Rust and can have bad side effects.
///
/// This is because procedural macros have Line/Column information available only in
/// **nightly** Rust. Instead, Lua chunks represented as a big single line of code in stable Rust.
///
/// As workaround, Rust comments `//` can be used.
///
/// Other minor limitations:
///
/// - Certain escape codes in string literals.
/// - Certain escape codes in string literals don't work.
/// (Specifically: `\a`, `\b`, `\f`, `\v`, `\123` (octal escape codes), `\u`, and `\U`).
///
/// These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`
/// These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`.
///
/// - The `//` (floor division) operator is unusable, as its start a comment.
///
/// Everything else should work.
///
/// [`AsChunk`]: trait.AsChunk.html
/// [`UserData`]: trait.UserData.html
/// [`ToLua`]: trait.ToLua.html
/// [`AsChunk`]: crate::AsChunk
/// [`UserData`]: crate::UserData
/// [`ToLua`]: crate::ToLua
#[cfg(any(feature = "macros"))]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
+573 -384
View File
File diff suppressed because it is too large Load Diff
+19 -4
View File
@@ -17,19 +17,19 @@ macro_rules! cstr {
macro_rules! mlua_panic {
($msg:expr) => {
panic!(bug_msg!($msg));
panic!(bug_msg!($msg))
};
($msg:expr,) => {
mlua_panic!($msg);
mlua_panic!($msg)
};
($msg:expr, $($arg:expr),+) => {
panic!(bug_msg!($msg), $($arg),+);
panic!(bug_msg!($msg), $($arg),+)
};
($msg:expr, $($arg:expr),+,) => {
mlua_panic!($msg, $($arg),+);
mlua_panic!($msg, $($arg),+)
};
}
@@ -94,3 +94,18 @@ macro_rules! require_module_feature {
compile_error!("Feature `module` must be enabled in the `mlua` crate");
};
}
macro_rules! protect_lua {
($state:expr, $nargs:expr, $nresults:expr, $f:expr) => {
crate::util::protect_lua_closure($state, $nargs, $nresults, $f)
};
($state:expr, $nargs:expr, $nresults:expr, fn($state_inner:ident) $code:expr) => {{
unsafe extern "C" fn do_call($state_inner: *mut ffi::lua_State) -> ::std::os::raw::c_int {
$code;
$nresults
}
crate::util::protect_lua_call($state, $nargs, do_call)
}};
}
+2 -2
View File
@@ -76,8 +76,8 @@ impl<'lua> FromLuaMulti<'lua> for MultiValue<'lua> {
/// # }
/// ```
///
/// [`FromLua`]: trait.FromLua.html
/// [`MultiValue`]: struct.MultiValue.html
/// [`FromLua`]: crate::FromLua
/// [`MultiValue`]: crate::MultiValue
#[derive(Debug, Clone)]
pub struct Variadic<T>(Vec<T>);
+7 -2
View File
@@ -1,5 +1,6 @@
//! Re-exports most types with an extra `Lua*` prefix to prevent name clashes.
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
@@ -14,8 +15,12 @@ pub use crate::{
};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::AsyncThread as LuaAsyncThread;
#[cfg(feature = "serialize")]
#[doc(inline)]
pub use crate::{LuaSerdeExt, SerializeOptions as LuaSerializeOptions};
#[doc(no_inline)]
pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt,
SerializeOptions as LuaSerializeOptions,
};
+44 -58
View File
@@ -13,20 +13,20 @@ use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{
assert_stack, check_stack, get_userdata, init_userdata_metatable, protect_lua, push_table,
rawset_field, take_userdata, StackGuard,
assert_stack, check_stack, get_userdata, init_userdata_metatable, push_table, rawset_field,
take_userdata, StackGuard,
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
futures_core::future::{Future, LocalBoxFuture},
crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue},
futures_core::future::Future,
futures_util::future::{self, TryFutureExt},
};
@@ -35,7 +35,7 @@ use {
///
/// See [`Lua::scope`] for more details.
///
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::scope`]: crate::Lua.html::scope
pub struct Scope<'lua, 'scope> {
lua: &'lua Lua,
destructors: RefCell<Vec<(LuaRef<'lua>, DestructorCallback<'lua>)>>,
@@ -58,8 +58,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// This is a version of [`Lua::create_function`] that creates a callback which expires on
/// scope drop. See [`Lua::scope`] for more details.
///
/// [`Lua::create_function`]: struct.Lua.html#method.create_function
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::create_function`]: crate::Lua::create_function
/// [`Lua::scope`]: crate::Lua::scope
pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
@@ -87,8 +87,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// This is a version of [`Lua::create_function_mut`] that creates a callback which expires
/// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
///
/// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::create_function_mut`]: crate::Lua::create_function_mut
/// [`Lua::scope`]: crate::Lua::scope
/// [`Scope::create_function`]: #method.create_function
pub fn create_function_mut<'callback, A, R, F>(
&'callback self,
@@ -114,9 +114,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
///
/// Requires `feature = "async"`
///
/// [`Lua::create_async_function`]: struct.Lua.html#method.create_async_function
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::async_scope`]: struct.Lua.html#method.async_scope
/// [`Lua::create_async_function`]: crate::Lua::create_async_function
/// [`Lua::scope`]: crate::Lua::scope
/// [`Lua::async_scope`]: crate::Lua::async_scope
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<'callback, A, R, F, FR>(
@@ -147,8 +147,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// UserData be 'static).
/// See [`Lua::scope`] for more details.
///
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::create_userdata`]: crate::Lua::create_userdata
/// [`Lua::scope`]: crate::Lua::scope
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
@@ -165,8 +165,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
///
/// Requires `feature = "serialize"`
///
/// [`Lua::create_ser_userdata`]: struct.Lua.html#method.create_ser_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::create_ser_userdata`]: crate::Lua::create_ser_userdata
/// [`Lua::scope`]: crate::Lua::scope
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
@@ -192,9 +192,10 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
ud.lua.push_ref(&ud);
// We know the destructor has not run yet because we hold a reference to the userdata.
// Check that userdata is not destructed (via `take()` call)
if ud.lua.push_userdata_ref(&ud).is_err() {
return vec![];
}
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
@@ -224,7 +225,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// use [`Scope::create_userdata`] instead.
///
/// The main limitation that comes from using non-'static userdata is that the produced userdata
/// will no longer have a `TypeId` associated with it, becuase `TypeId` can only work for
/// will no longer have a `TypeId` associated with it, because `TypeId` can only work for
/// 'static types. This means that it is impossible, once the userdata is created, to get a
/// reference to it back *out* of an `AnyUserData` handle. This also implies that the
/// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept
@@ -233,9 +234,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// creating the userdata metatable each time a new userdata is created.
///
/// [`Scope::create_userdata`]: #method.create_userdata
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserDataMethods`]: trait.UserDataMethods.html
/// [`Lua::create_userdata`]: crate::Lua::create_userdata
/// [`Lua::scope`]:crate::Lua::scope
/// [`UserDataMethods`]: crate::UserDataMethods
pub fn create_nonstatic_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'scope + UserData,
@@ -250,7 +251,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
data: Rc<RefCell<T>>,
data_ptr: *mut c_void,
data_ptr: *const c_void,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
// On methods that actually receive the userdata, we fake a type check on the passed in
@@ -264,9 +265,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
if let Some(Value::UserData(ud)) = value {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&ud.0, false)?;
if get_userdata(lua.state, -1) == data_ptr {
check_stack(lua.state, 2)?;
lua.push_userdata_ref(&ud.0)?;
if get_userdata(lua.state, -1) as *const _ == data_ptr {
return Ok(());
}
}
@@ -322,7 +323,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 13)?;
let data_ptr = protect_lua(lua.state, 0, 1, |state| {
let data_ptr = protect_lua!(lua.state, 0, 1, |state| {
ffi::lua_newuserdata(state, mem::size_of::<UserDataCell<Rc<RefCell<T>>>>())
})?;
// Prepare metatable, add meta methods first and then meta fields
@@ -390,12 +391,12 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
+ methods_index.map(|_| 1).unwrap_or(0);
ffi::lua_pop(lua.state, count);
let mt_id = ffi::lua_topointer(lua.state, -1);
let mt_ptr = ffi::lua_topointer(lua.state, -1);
// Write userdata just before attaching metatable with `__gc` metamethod
ptr::write(data_ptr as _, UserDataCell::new(data));
ffi::lua_setmetatable(lua.state, -2);
let ud = AnyUserData(lua.pop_ref());
lua.register_userdata_metatable(mt_id as isize);
lua.register_userdata_metatable(mt_ptr, None);
#[cfg(any(feature = "lua51", feature = "luajit"))]
let newtable = lua.create_table()?;
@@ -404,15 +405,16 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
ud.lua.push_ref(&ud);
// We know the destructor has not run yet because we hold a reference to the userdata.
// Check that userdata is valid (very likely)
if ud.lua.push_userdata_ref(&ud).is_err() {
return vec![];
}
// Deregister metatable
ffi::lua_getmetatable(state, -1);
let mt_id = ffi::lua_topointer(state, -1);
let mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
ud.lua.deregister_userdata_metatable(mt_id as isize);
ud.lua.deregister_userdata_metatable(mt_ptr);
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
@@ -460,16 +462,11 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// We know the destructor has not run yet because we hold a reference to the callback.
ffi::lua_getupvalue(state, -1, 1);
let ud1 = take_userdata::<Callback>(state);
let ud = take_userdata::<CallbackUpvalue>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
ffi::lua_getupvalue(state, -1, 2);
let ud2 = take_userdata::<Lua>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 2);
vec![Box::new(ud1), Box::new(ud2)]
vec![Box::new(ud)]
});
self.destructors
.borrow_mut()
@@ -510,32 +507,21 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// Destroy all upvalues
ffi::lua_getupvalue(state, -1, 1);
let ud1 = take_userdata::<AsyncCallback>(state);
let upvalue1 = take_userdata::<AsyncCallbackUpvalue>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
ffi::lua_getupvalue(state, -1, 2);
let ud2 = take_userdata::<Lua>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 2);
ffi::lua_pop(state, 1);
let mut data: Vec<Box<dyn Any>> = vec![Box::new(ud1), Box::new(ud2)];
let mut data: Vec<Box<dyn Any>> = vec![Box::new(upvalue1)];
// Finally, get polled future and destroy it
f.lua.push_ref(&poll_str.0);
if ffi::lua_rawget(state, -2) == ffi::LUA_TFUNCTION {
ffi::lua_getupvalue(state, -1, 1);
let ud3 = take_userdata::<LocalBoxFuture<Result<MultiValue>>>(state);
let upvalue2 = take_userdata::<AsyncPollUpvalue>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
data.push(Box::new(ud3));
ffi::lua_getupvalue(state, -1, 2);
let ud4 = take_userdata::<Lua>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 2);
data.push(Box::new(ud4));
data.push(Box::new(upvalue2));
}
data
+264 -48
View File
@@ -1,19 +1,105 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::os::raw::c_void;
use std::rc::Rc;
use std::string::String as StdString;
use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result};
use crate::table::{TablePairs, TableSequence};
use crate::ffi;
use crate::table::{Table, TablePairs, TableSequence};
use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
#[derive(Debug)]
pub struct Deserializer<'lua>(Value<'lua>);
pub struct Deserializer<'lua> {
value: Value<'lua>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
}
/// A struct with options to change default deserializer behavior.
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Options {
/// If true, an attempt to serialize types such as [`Thread`], [`UserData`], [`LightUserData`]
/// and [`Error`] will cause an error.
/// Otherwise these types skipped when iterating or serialized as unit type.
///
/// Default: **true**
///
/// [`Thread`]: crate::Thread
/// [`UserData`]: crate::UserData
/// [`LightUserData`]: crate::LightUserData
/// [`Error`]: crate::Error
pub deny_unsupported_types: bool,
/// If true, an attempt to serialize a recursive table (table that refers to itself)
/// will cause an error.
/// Otherwise subsequent attempts to serialize the same table will be ignored.
///
/// Default: **true**
pub deny_recursive_tables: bool,
}
impl Default for Options {
fn default() -> Self {
Options {
deny_unsupported_types: true,
deny_recursive_tables: true,
}
}
}
impl Options {
/// Returns a new instance of `Options` with default parameters.
pub fn new() -> Self {
Self::default()
}
/// Sets [`deny_unsupported_types`] option.
///
/// [`deny_unsupported_types`]: #structfield.deny_unsupported_types
pub fn deny_unsupported_types(mut self, enabled: bool) -> Self {
self.deny_unsupported_types = enabled;
self
}
/// Sets [`deny_recursive_tables`] option.
///
/// [`deny_recursive_tables`]: #structfield.deny_recursive_tables
pub fn deny_recursive_tables(mut self, enabled: bool) -> Self {
self.deny_recursive_tables = enabled;
self
}
}
impl<'lua> Deserializer<'lua> {
/// Creates a new Lua Deserializer for the `Value`.
pub fn new(value: Value<'lua>) -> Self {
Deserializer(value)
Self::new_with_options(value, Options::default())
}
/// Creates a new Lua Deserializer for the `Value` with custom options.
pub fn new_with_options(value: Value<'lua>, options: Options) -> Self {
Deserializer {
value,
options,
visited: Rc::new(RefCell::new(HashSet::new())),
}
}
fn from_parts(
value: Value<'lua>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
) -> Self {
Deserializer {
value,
options,
visited,
}
}
}
@@ -25,7 +111,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
where
V: de::Visitor<'de>,
{
match self.0 {
match self.value {
Value::Nil => visitor.visit_unit(),
Value::Boolean(b) => visitor.visit_bool(b),
#[allow(clippy::useless_conversion)]
@@ -43,7 +129,16 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_) => Err(de::Error::custom("invalid value type")),
| Value::Error(_) => {
if self.options.deny_unsupported_types {
Err(de::Error::custom(format!(
"unsupported value type `{}`",
self.value.type_name()
)))
} else {
visitor.visit_unit()
}
}
}
}
@@ -52,7 +147,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
where
V: de::Visitor<'de>,
{
match self.0 {
match self.value {
Value::Nil => visitor.visit_none(),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
_ => visitor.visit_some(self),
@@ -69,9 +164,11 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
where
V: de::Visitor<'de>,
{
let (variant, value) = match self.0 {
Value::Table(value) => {
let mut iter = value.pairs::<StdString, Value>();
let (variant, value, _guard) = match self.value {
Value::Table(table) => {
let _guard = RecursionGuard::new(&table, &self.visited);
let mut iter = table.pairs::<StdString, Value>();
let (variant, value) = match iter.next() {
Some(v) => v?,
None => {
@@ -88,13 +185,22 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
&"map with a single key",
));
}
(variant, Some(value))
if check_value_if_skip(&value, self.options, &self.visited)? {
return Err(de::Error::custom("bad enum value"));
}
(variant, Some(value), Some(_guard))
}
Value::String(variant) => (variant.to_str()?.to_owned(), None),
Value::String(variant) => (variant.to_str()?.to_owned(), None, None),
_ => return Err(de::Error::custom("bad enum value")),
};
visitor.visit_enum(EnumDeserializer { variant, value })
visitor.visit_enum(EnumDeserializer {
variant,
value,
options: self.options,
visited: self.visited,
})
}
#[inline]
@@ -102,12 +208,18 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
where
V: de::Visitor<'de>,
{
match self.0 {
match self.value {
Value::Table(t) => {
let _guard = RecursionGuard::new(&t, &self.visited);
let len = t.raw_len() as usize;
let mut deserializer = SeqDeserializer(t.raw_sequence_values());
let mut deserializer = SeqDeserializer {
seq: t.raw_sequence_values(),
options: self.options,
visited: self.visited,
};
let seq = visitor.visit_seq(&mut deserializer)?;
if deserializer.0.count() == 0 {
if deserializer.seq.count() == 0 {
Ok(seq)
} else {
Err(de::Error::invalid_length(
@@ -116,7 +228,10 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
))
}
}
_ => Err(de::Error::custom("invalid value type")),
value => Err(de::Error::invalid_type(
de::Unexpected::Other(value.type_name()),
&"table",
)),
}
}
@@ -146,9 +261,17 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
where
V: de::Visitor<'de>,
{
match self.0 {
match self.value {
Value::Table(t) => {
let mut deserializer = MapDeserializer::new(t.pairs());
let _guard = RecursionGuard::new(&t, &self.visited);
let mut deserializer = MapDeserializer {
pairs: t.pairs(),
value: None,
options: self.options,
visited: self.visited,
processed: 0,
};
let map = visitor.visit_map(&mut deserializer)?;
let count = deserializer.pairs.count();
if count == 0 {
@@ -160,7 +283,10 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
))
}
}
_ => Err(de::Error::custom("invalid value type")),
value => Err(de::Error::invalid_type(
de::Unexpected::Other(value.type_name()),
&"table",
)),
}
}
@@ -178,13 +304,17 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 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 newtype_struct
identifier ignored_any
}
}
struct SeqDeserializer<'lua>(TableSequence<'lua, Value<'lua>>);
struct SeqDeserializer<'lua> {
seq: TableSequence<'lua, Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
}
impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
type Error = Error;
@@ -193,14 +323,24 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
where
T: de::DeserializeSeed<'de>,
{
match self.0.next() {
Some(value) => seed.deserialize(Deserializer(value?)).map(Some),
None => Ok(None),
loop {
match self.seq.next() {
Some(value) => {
let value = value?;
if check_value_if_skip(&value, self.options, &self.visited)? {
continue;
}
let visited = Rc::clone(&self.visited);
let deserializer = Deserializer::from_parts(value, self.options, visited);
return seed.deserialize(deserializer).map(Some);
}
None => return Ok(None),
}
}
}
fn size_hint(&self) -> Option<usize> {
match self.0.size_hint() {
match self.seq.size_hint() {
(lower, Some(upper)) if lower == upper => Some(upper),
_ => None,
}
@@ -210,19 +350,11 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
struct MapDeserializer<'lua> {
pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>,
value: Option<Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
processed: usize,
}
impl<'lua> MapDeserializer<'lua> {
fn new(pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>) -> Self {
MapDeserializer {
pairs,
value: None,
processed: 0,
}
}
}
impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
type Error = Error;
@@ -230,15 +362,23 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
where
T: de::DeserializeSeed<'de>,
{
match self.pairs.next() {
Some(item) => {
let (key, value) = item?;
self.processed += 1;
self.value = Some(value);
let key_de = Deserializer(key);
seed.deserialize(key_de).map(Some)
loop {
match self.pairs.next() {
Some(item) => {
let (key, value) = item?;
if check_value_if_skip(&key, self.options, &self.visited)?
|| check_value_if_skip(&value, self.options, &self.visited)?
{
continue;
}
self.processed += 1;
self.value = Some(value);
let visited = Rc::clone(&self.visited);
let key_de = Deserializer::from_parts(key, self.options, visited);
return seed.deserialize(key_de).map(Some);
}
None => return Ok(None),
}
None => Ok(None),
}
}
@@ -247,7 +387,10 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
T: de::DeserializeSeed<'de>,
{
match self.value.take() {
Some(value) => seed.deserialize(Deserializer(value)),
Some(value) => {
let visited = Rc::clone(&self.visited);
seed.deserialize(Deserializer::from_parts(value, self.options, visited))
}
None => Err(de::Error::custom("value is missing")),
}
}
@@ -263,6 +406,8 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
struct EnumDeserializer<'lua> {
variant: StdString,
value: Option<Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
}
impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
@@ -274,13 +419,19 @@ impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
T: de::DeserializeSeed<'de>,
{
let variant = self.variant.into_deserializer();
let variant_access = VariantDeserializer { value: self.value };
let variant_access = VariantDeserializer {
value: self.value,
options: self.options,
visited: self.visited,
};
seed.deserialize(variant).map(|v| (v, variant_access))
}
}
struct VariantDeserializer<'lua> {
value: Option<Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
}
impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
@@ -301,7 +452,9 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
T: de::DeserializeSeed<'de>,
{
match self.value {
Some(value) => seed.deserialize(Deserializer(value)),
Some(value) => {
seed.deserialize(Deserializer::from_parts(value, self.options, self.visited))
}
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"newtype variant",
@@ -314,7 +467,10 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_seq(Deserializer(value), visitor),
Some(value) => serde::Deserializer::deserialize_seq(
Deserializer::from_parts(value, self.options, self.visited),
visitor,
),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"tuple variant",
@@ -327,7 +483,10 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_map(Deserializer(value), visitor),
Some(value) => serde::Deserializer::deserialize_map(
Deserializer::from_parts(value, self.options, self.visited),
visitor,
),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"struct variant",
@@ -335,3 +494,60 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
}
}
}
// Adds `ptr` to the `visited` map and removes on drop
// Used to track recursive tables but allow to traverse same tables multiple times
struct RecursionGuard {
ptr: *const c_void,
visited: Rc<RefCell<HashSet<*const c_void>>>,
}
impl RecursionGuard {
#[inline]
fn new(table: &Table, visited: &Rc<RefCell<HashSet<*const c_void>>>) -> Self {
let visited = Rc::clone(visited);
let lua = table.0.lua;
let ptr =
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
visited.borrow_mut().insert(ptr);
RecursionGuard { ptr, visited }
}
}
impl Drop for RecursionGuard {
fn drop(&mut self) {
self.visited.borrow_mut().remove(&self.ptr);
}
}
// Checks `options` and decides should we emit an error or skip next element
fn check_value_if_skip(
value: &Value,
options: Options,
visited: &RefCell<HashSet<*const c_void>>,
) -> Result<bool> {
match value {
Value::Table(table) => {
let lua = table.0.lua;
let ptr =
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
if visited.borrow().contains(&ptr) {
if options.deny_recursive_tables {
return Err(de::Error::custom("recursive table detected"));
}
return Ok(true); // skip
}
}
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_)
if !options.deny_unsupported_types =>
{
return Ok(true); // skip
}
_ => {}
}
Ok(false) // do not skip
}
+52 -10
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, protect_lua, StackGuard};
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde.
@@ -69,11 +69,11 @@ pub trait LuaSerdeExt<'lua> {
/// ```
fn array_metatable(&'lua self) -> Table<'lua>;
/// Converts `T` into a `Value` instance.
/// Converts `T` into a [`Value`] instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
/// [`Value`]: crate::Value
///
/// # Example
///
@@ -102,11 +102,11 @@ pub trait LuaSerdeExt<'lua> {
/// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Converts `T` into a `Value` instance with options.
/// Converts `T` into a [`Value`] instance with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
/// [`Value`]: crate::Value
///
/// # Example
///
@@ -129,11 +129,11 @@ pub trait LuaSerdeExt<'lua> {
where
T: Serialize + ?Sized;
/// Deserializes a `Value` into any serde deserializable object.
/// Deserializes a [`Value`] into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
/// [`Value`]: crate::Value
///
/// # Example
///
@@ -158,6 +158,41 @@ pub trait LuaSerdeExt<'lua> {
/// }
/// ```
fn from_value<T: Deserialize<'lua>>(&'lua self, value: Value<'lua>) -> Result<T>;
/// Deserializes a [`Value`] into any serde deserializable object with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt, DeserializeOptions};
/// use serde::Deserialize;
///
/// #[derive(Deserialize, Debug, PartialEq)]
/// struct User {
/// name: String,
/// age: u8,
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let val = lua.load(r#"{name = "John Smith", age = 20, f = function() end}"#).eval()?;
/// let options = DeserializeOptions::new().deny_unsupported_types(false);
/// let u: User = lua.from_value_with(val, options)?;
///
/// assert_eq!(u, User { name: "John Smith".into(), age: 20 });
///
/// Ok(())
/// }
/// ```
fn from_value_with<T: Deserialize<'lua>>(
&'lua self,
value: Value<'lua>,
options: de::Options,
) -> Result<T>;
}
impl<'lua> LuaSerdeExt<'lua> for Lua {
@@ -196,12 +231,19 @@ impl<'lua> LuaSerdeExt<'lua> for Lua {
{
T::deserialize(de::Deserializer::new(value))
}
fn from_value_with<T>(&'lua self, value: Value<'lua>, options: de::Options) -> Result<T>
where
T: Deserialize<'lua>,
{
T::deserialize(de::Deserializer::new_with_options(value, options))
}
}
// Uses 6 stack spaces and calls checkstack.
// Uses 2 stack spaces and calls checkstack.
pub(crate) unsafe fn init_metatables(state: *mut ffi::lua_State) -> Result<()> {
check_stack(state, 3)?;
protect_lua(state, 0, 0, |state| {
check_stack(state, 2)?;
protect_lua!(state, 0, 0, fn(state) {
ffi::lua_createtable(state, 0, 1);
ffi::lua_pushstring(state, cstr!("__metatable"));
+13 -11
View File
@@ -9,7 +9,7 @@ use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::types::Integer;
use crate::util::{check_stack, protect_lua, StackGuard};
use crate::util::{check_stack, StackGuard};
use crate::value::{ToLua, Value};
/// A struct for serializing Rust values into Lua values.
@@ -28,16 +28,16 @@ pub struct Options {
///
/// Default: **true**
///
/// [`array_metatable`]: ../trait.LuaSerdeExt.html#tymethod.array_metatable
/// [`array_metatable`]: crate::LuaSerdeExt::array_metatable
pub set_array_metatable: bool,
/// If true, serialize `None` (part of `Option` type) to [`null`].
/// If true, serialize `None` (part of the `Option` type) to [`null`].
/// Otherwise it will be set to Lua [`Nil`].
///
/// Default: **true**
///
/// [`null`]: ../trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../../enum.Value.html#variant.Nil
/// [`null`]: crate::LuaSerdeExt::null
/// [`Nil`]: crate::Value::Nil
pub serialize_none_to_null: bool,
/// If true, serialize `Unit` (type of `()` in Rust) and Unit structs to [`null`].
@@ -45,8 +45,8 @@ pub struct Options {
///
/// Default: **true**
///
/// [`null`]: ../trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../../enum.Value.html#variant.Nil
/// [`null`]: crate::LuaSerdeExt::null
/// [`Nil`]: crate::Value::Nil
pub serialize_unit_to_null: bool,
}
@@ -61,7 +61,7 @@ impl Default for Options {
}
impl Options {
/// Retruns a new instance of `Options` with default parameters.
/// Returns a new instance of [`Options`] with default parameters.
pub fn new() -> Self {
Self::default()
}
@@ -139,6 +139,8 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
lua_serialize_number!(serialize_u32, u32);
lua_serialize_number!(serialize_i64, i64);
lua_serialize_number!(serialize_u64, u64);
lua_serialize_number!(serialize_i128, i128);
lua_serialize_number!(serialize_u128, u128);
lua_serialize_number!(serialize_f32, f32);
lua_serialize_number!(serialize_f64, f64);
@@ -318,12 +320,12 @@ impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
let value = lua.to_value_with(value, self.options)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
check_stack(lua.state, 4)?;
lua.push_ref(&self.table.0);
lua.push_value(value)?;
let len = ffi::lua_rawlen(lua.state, -2) as Integer;
protect_lua(lua.state, 2, 0, |state| {
protect_lua!(lua.state, 2, 0, fn(state) {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
})
}
+19 -20
View File
@@ -10,7 +10,7 @@ use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::types::{Integer, LuaRef};
use crate::util::{assert_stack, check_stack, protect_lua, StackGuard};
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::{FromLua, FromLuaMulti, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
@@ -62,12 +62,12 @@ impl<'lua> Table<'lua> {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 6)?;
check_stack(lua.state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
protect_lua(lua.state, 3, 0, |state| ffi::lua_settable(state, -3))
protect_lua!(lua.state, 3, 0, fn(state) ffi::lua_settable(state, -3))
}
}
@@ -101,11 +101,11 @@ impl<'lua> Table<'lua> {
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
protect_lua(lua.state, 2, 1, |state| ffi::lua_gettable(state, -2))?;
protect_lua!(lua.state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
lua.pop_value()
};
@@ -119,13 +119,12 @@ impl<'lua> Table<'lua> {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
protect_lua(lua.state, 2, 1, |state| {
ffi::lua_gettable(state, -2) != ffi::LUA_TNIL
})
protect_lua!(lua.state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
Ok(ffi::lua_isnil(lua.state, -1) == 0)
}
}
@@ -193,12 +192,12 @@ impl<'lua> Table<'lua> {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 6)?;
check_stack(lua.state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
protect_lua(lua.state, 3, 0, |state| ffi::lua_rawset(state, -3))
protect_lua!(lua.state, 3, 0, fn(state) ffi::lua_rawset(state, -3))
}
}
@@ -236,7 +235,7 @@ impl<'lua> Table<'lua> {
lua.push_ref(&self.0);
lua.push_value(value)?;
protect_lua(lua.state, 2, 0, |state| {
protect_lua!(lua.state, 2, 0, |state| {
for i in (idx..=size).rev() {
// table[i+1] = table[i]
ffi::lua_rawgeti(state, -2, i);
@@ -268,7 +267,7 @@ impl<'lua> Table<'lua> {
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
protect_lua(lua.state, 1, 0, |state| {
protect_lua!(lua.state, 1, 0, |state| {
for i in idx..size {
ffi::lua_rawgeti(state, -1, i + 1);
ffi::lua_rawseti(state, -2, i);
@@ -294,7 +293,7 @@ impl<'lua> Table<'lua> {
check_stack(lua.state, 4)?;
lua.push_ref(&self.0);
protect_lua(lua.state, 1, 0, |state| ffi::luaL_len(state, -1))
protect_lua!(lua.state, 1, 0, |state| ffi::luaL_len(state, -1))
}
}
@@ -379,7 +378,7 @@ impl<'lua> Table<'lua> {
/// # }
/// ```
///
/// [`Result`]: type.Result.html
/// [`Result`]: crate::Result
/// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next
pub fn pairs<K: FromLua<'lua>, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> {
TablePairs {
@@ -428,7 +427,7 @@ impl<'lua> Table<'lua> {
/// ```
///
/// [`pairs`]: #method.pairs
/// [`Result`]: type.Result.html
/// [`Result`]: crate::Result
/// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next
pub fn sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
TableSequence {
@@ -646,7 +645,7 @@ impl<'lua> Serialize for Table<'lua> {
///
/// This struct is created by the [`Table::pairs`] method.
///
/// [`Table::pairs`]: struct.Table.html#method.pairs
/// [`Table::pairs`]: crate::Table::pairs
pub struct TablePairs<'lua, K, V> {
table: LuaRef<'lua>,
key: Option<Value<'lua>>,
@@ -671,7 +670,7 @@ where
lua.push_ref(&self.table);
lua.push_value(prev_key)?;
let next = protect_lua(lua.state, 2, ffi::LUA_MULTRET, |state| {
let next = protect_lua!(lua.state, 2, ffi::LUA_MULTRET, |state| {
ffi::lua_next(state, -2)
})?;
if next != 0 {
@@ -705,7 +704,7 @@ where
///
/// This struct is created by the [`Table::sequence_values`] method.
///
/// [`Table::sequence_values`]: struct.Table.html#method.sequence_values
/// [`Table::sequence_values`]: crate::Table::sequence_values
pub struct TableSequence<'lua, V> {
table: LuaRef<'lua>,
index: Option<Integer>,
@@ -732,7 +731,7 @@ where
let res = if self.raw {
ffi::lua_rawgeti(lua.state, -1, index)
} else {
protect_lua(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index))?
protect_lua!(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index))?
};
match res {
ffi::LUA_TNIL if index > self.len.unwrap_or(0) => Ok(None),
+32 -44
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::{assert_stack, check_stack, error_traceback, pop_error, protect_lua, StackGuard};
use crate::util::{check_stack, error_traceback, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored"), doc))]
@@ -13,15 +13,13 @@ use crate::function::Function;
#[cfg(feature = "async")]
use {
crate::{
lua::{ASYNC_POLL_PENDING, WAKER_REGISTRY_KEY},
util::get_gc_userdata,
lua::{Lua, ASYNC_POLL_PENDING},
value::Value,
},
futures_core::{future::Future, stream::Stream},
std::{
cell::RefCell,
marker::PhantomData,
mem,
os::raw::c_void,
pin::Pin,
task::{Context, Poll, Waker},
@@ -35,7 +33,7 @@ pub enum ThreadStatus {
///
/// If a thread is in this state, it can be resumed by calling [`Thread::resume`].
///
/// [`Thread::resume`]: struct.Thread.html#method.resume
/// [`Thread::resume`]: crate::Thread::resume
Resumable,
/// Either the thread has finished executing, or the thread is currently running.
Unresumable,
@@ -51,8 +49,8 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
///
/// Requires `feature = "async"`
///
/// [`Future`]: ../futures_core/future/trait.Future.html
/// [`Stream`]: ../futures_core/stream/trait.Stream.html
/// [`Future`]: futures_core::future::Future
/// [`Stream`]: futures_core::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug)]
@@ -114,11 +112,10 @@ impl<'lua> Thread<'lua> {
let nargs = args.len() as c_int;
let results = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, cmp::min(nargs + 1, 3))?;
check_stack(lua.state, cmp::max(nargs + 1, 3))?;
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1);
ffi::lua_pop(lua.state, 1);
let thread_state =
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 {
@@ -135,7 +132,7 @@ 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))?;
protect_lua!(lua.state, 0, 0, |_| error_traceback(thread_state))?;
return Err(pop_error(thread_state, ret));
}
@@ -155,12 +152,8 @@ impl<'lua> Thread<'lua> {
pub fn status(&self) -> ThreadStatus {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1);
ffi::lua_pop(lua.state, 1);
let thread_state =
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
@@ -210,10 +203,10 @@ impl<'lua> Thread<'lua> {
}
}
/// Converts Thread to an AsyncThread which implements Future and Stream traits.
/// Converts Thread to an AsyncThread which implements [`Future`] and [`Stream`] traits.
///
/// `args` are passed as arguments to the thread function for first call.
/// The object call `resume()` while polling and also allows to run rust futures
/// The object calls [`resume()`] while polling and also allows to run rust futures
/// to completion using an executor.
///
/// Using AsyncThread as a Stream allows to iterate through `coroutine.yield()`
@@ -222,6 +215,10 @@ impl<'lua> Thread<'lua> {
///
/// Requires `feature = "async"`
///
/// [`Future`]: futures_core::future::Future
/// [`Stream`]: futures_core::stream::Stream
/// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
///
/// # Examples
///
/// ```
@@ -288,7 +285,7 @@ where
_ => return Poll::Ready(None),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let _wg = WakerGuard::new(lua, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
@@ -319,7 +316,7 @@ where
_ => return Poll::Ready(Err(Error::CoroutineInactive)),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let _wg = WakerGuard::new(lua, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
@@ -341,9 +338,10 @@ where
}
#[cfg(feature = "async")]
#[inline(always)]
fn is_poll_pending(val: &MultiValue) -> bool {
match val.iter().enumerate().last() {
Some((1, Value::LightUserData(ud))) => {
Some((0, Value::LightUserData(ud))) => {
ud.0 == &ASYNC_POLL_PENDING as *const u8 as *mut c_void
}
_ => false,
@@ -351,37 +349,27 @@ fn is_poll_pending(val: &MultiValue) -> bool {
}
#[cfg(feature = "async")]
struct WakerGuard(*mut ffi::lua_State, Option<Waker>);
struct WakerGuard<'lua> {
lua: &'lua Lua,
prev: Option<Waker>,
}
#[cfg(feature = "async")]
impl WakerGuard {
pub fn new(state: *mut ffi::lua_State, waker: Waker) -> Result<WakerGuard> {
impl<'lua> WakerGuard<'lua> {
#[inline]
pub fn new(lua: &Lua, waker: Waker) -> Result<WakerGuard> {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
let waker_slot = get_gc_userdata::<Option<Waker>>(state, -1).as_mut();
let old = mlua_expect!(waker_slot, "Waker is destroyed").replace(waker);
Ok(WakerGuard(state, old))
let prev = lua.set_waker(Some(waker));
Ok(WakerGuard { lua, prev })
}
}
}
#[cfg(feature = "async")]
impl Drop for WakerGuard {
impl<'lua> Drop for WakerGuard<'lua> {
fn drop(&mut self) {
let state = self.0;
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 3);
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
let waker_slot = get_gc_userdata::<Option<Waker>>(state, -1).as_mut();
mem::swap(mlua_expect!(waker_slot, "Waker is destroyed"), &mut self.1);
self.lua.set_waker(self.prev.take());
}
}
}
+32 -7
View File
@@ -26,10 +26,31 @@ pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
pub(crate) struct CallbackUpvalue<'lua> {
pub(crate) lua: Lua,
pub(crate) func: Callback<'lua, 'static>,
}
#[cfg(feature = "async")]
pub(crate) type AsyncCallback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<MultiValue<'lua>>> + 'a>;
#[cfg(feature = "async")]
pub(crate) struct AsyncCallbackUpvalue<'lua> {
pub(crate) lua: Lua,
pub(crate) func: AsyncCallback<'lua, 'static>,
}
#[cfg(feature = "async")]
pub(crate) struct AsyncPollUpvalue<'lua> {
pub(crate) lua: Lua,
pub(crate) fut: LocalBoxFuture<'lua, Result<MultiValue<'lua>>>,
}
#[cfg(feature = "send")]
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()> + Send>>;
#[cfg(not(feature = "send"))]
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>;
#[cfg(feature = "send")]
@@ -42,21 +63,25 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
pub(crate) struct DestructedUserdataMT;
/// An auto generated key into the Lua registry.
///
/// This is a handle to a value stored inside the Lua registry. It is not automatically
/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`],
/// and instances not manually removed can be garbage collected with [`Lua::expire_registry_values`].
///
/// Be warned, If you place this into Lua via a `UserData` type or a rust callback, it is *very
/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
/// Instead of placing a `RegistryKey` into a `UserData` type, prefer instead to use
/// [`UserData::set_user_value`] / [`UserData::get_user_value`].
/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use
/// [`AnyUserData::set_user_value`] / [`AnyUserData::get_user_value`].
///
/// [`Lua::remove_registry_value`]: struct.Lua.html#method.remove_registry_value
/// [`Lua::expire_registry_values`]: struct.Lua.html#method.expire_registry_values
/// [`UserData::set_user_value`]: struct.UserData.html#method.set_user_value
/// [`UserData::get_user_value`]: struct.UserData.html#method.get_user_value
/// [`UserData`]: crate::UserData
/// [`RegistryKey`]: crate::RegistryKey
/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value
/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
/// [`AnyUserData::get_user_value`]: crate::AnyUserData::get_user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
+113 -174
View File
@@ -20,9 +20,7 @@ use crate::function::Function;
use crate::lua::Lua;
use crate::table::{Table, TablePairs};
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::util::{
check_stack, get_destructed_userdata_metatable, get_userdata, push_string, StackGuard,
};
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
@@ -36,7 +34,7 @@ use crate::types::AsyncCallback;
/// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is
/// generally no need to do so: [`UserData`] implementors can instead just implement `Drop`.
///
/// [`UserData`]: trait.UserData.html
/// [`UserData`]: crate::UserData
#[derive(Debug, Clone)]
pub enum MetaMethod {
/// The `+` operator.
@@ -273,7 +271,7 @@ impl From<&str> for MetaMethod {
/// Method registry for [`UserData`] implementors.
///
/// [`UserData`]: trait.UserData.html
/// [`UserData`]: crate::UserData
pub trait UserDataMethods<'lua, T: UserData> {
/// Add a regular method which accepts a `&T` as the first parameter.
///
@@ -327,7 +325,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
///
/// Prefer to use [`add_method`] or [`add_method_mut`] as they are easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_method`]: #method.add_method
/// [`add_method_mut`]: #method.add_method_mut
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
@@ -438,7 +436,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// Field registry for [`UserData`] implementors.
///
/// [`UserData`]: trait.UserData.html
/// [`UserData`]: crate::UserData
pub trait UserDataFields<'lua, T: UserData> {
/// Add a regular field getter as a method which accepts a `&T` as the parameter.
///
@@ -471,7 +469,7 @@ pub trait UserDataFields<'lua, T: UserData> {
///
/// Prefer to use [`add_field_method_get`] as it is easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_field_method_get`]: #method.add_field_method_get
fn add_field_function_get<S, R, F>(&mut self, name: &S, function: F)
where
@@ -484,7 +482,7 @@ pub trait UserDataFields<'lua, T: UserData> {
///
/// Prefer to use [`add_field_method_set`] as it is easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_field_method_set`]: #method.add_field_method_set
fn add_field_function_set<S, A, F>(&mut self, name: &S, function: F)
where
@@ -519,8 +517,10 @@ pub trait UserDataFields<'lua, T: UserData> {
/// Trait for custom userdata types.
///
/// By implementing this trait, a struct becomes eligible for use inside Lua code. Implementations
/// of [`ToLua`] and [`FromLua`] are automatically provided.
/// By implementing this trait, a struct becomes eligible for use inside Lua code.
/// Implementation of [`ToLua`] is automatically provided, [`FromLua`] is implemented
/// only for `T: UserData + Clone`.
///
///
/// # Examples
///
@@ -578,10 +578,10 @@ pub trait UserDataFields<'lua, T: UserData> {
/// # }
/// ```
///
/// [`ToLua`]: trait.ToLua.html
/// [`FromLua`]: trait.FromLua.html
/// [`UserDataFields`]: trait.UserDataFields.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
/// [`ToLua`]: crate::ToLua
/// [`FromLua`]: crate::FromLua
/// [`UserDataFields`]: crate::UserDataFields
/// [`UserDataMethods`]: crate::UserDataMethods
pub trait UserData: Sized {
/// Adds custom fields specific to this userdata.
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(_fields: &mut F) {}
@@ -594,11 +594,13 @@ pub trait UserData: Sized {
pub(crate) struct UserDataCell<T>(RefCell<UserDataWrapped<T>>);
impl<T> UserDataCell<T> {
#[inline]
pub(crate) fn new(data: T) -> Self {
UserDataCell(RefCell::new(UserDataWrapped::new(data)))
}
#[cfg(feature = "serialize")]
#[inline]
pub(crate) fn new_ser(data: T) -> Self
where
T: 'static + Serialize,
@@ -607,139 +609,57 @@ impl<T> UserDataCell<T> {
}
// Immutably borrows the wrapped value.
fn try_borrow(&self) -> Result<UserDataRef<T>> {
#[inline]
pub(crate) fn try_borrow(&self) -> Result<Ref<T>> {
self.0
.try_borrow()
.map(|r| UserDataRef(UserDataRefInner::Ref(r)))
.map(|r| Ref::map(r, |r| r.deref()))
.map_err(|_| Error::UserDataBorrowError)
}
// Mutably borrows the wrapped value.
fn try_borrow_mut(&self) -> Result<UserDataRefMut<T>> {
#[inline]
pub(crate) fn try_borrow_mut(&self) -> Result<RefMut<T>> {
self.0
.try_borrow_mut()
.map(|r| UserDataRefMut(UserDataRefMutInner::Ref(r)))
.map(|r| RefMut::map(r, |r| r.deref_mut()))
.map_err(|_| Error::UserDataBorrowMutError)
}
}
#[cfg(feature = "serialize")]
impl Serialize for UserDataCell<()> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let ser = self
.0
.try_borrow()
.map_err(|_| ser::Error::custom(Error::UserDataBorrowError))?
.ser;
unsafe { (&*ser).serialize(serializer) }
// Consumes this `UserDataCell`, returning the wrapped value.
#[inline]
fn into_inner(self) -> T {
self.0.into_inner().into_inner()
}
}
/// A wrapper type for an immutably borrowed value from an `AnyUserData`.
pub struct UserDataRef<'a, T>(UserDataRefInner<'a, T>);
enum UserDataRefInner<'a, T> {
Ref(Ref<'a, UserDataWrapped<T>>),
}
/// A wrapper type for a mutably borrowed value from an `AnyUserData`.
pub struct UserDataRefMut<'a, T>(UserDataRefMutInner<'a, T>);
enum UserDataRefMutInner<'a, T> {
Ref(RefMut<'a, UserDataWrapped<T>>),
}
impl<T> Deref for UserDataRef<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match &self.0 {
UserDataRefInner::Ref(x) => &*x,
}
}
}
impl<T> Deref for UserDataRefMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match &self.0 {
UserDataRefMutInner::Ref(x) => &*x,
}
}
}
impl<T> DerefMut for UserDataRefMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.0 {
UserDataRefMutInner::Ref(x) => &mut *x,
}
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&*self as &T, f)
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&*self as &T, f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self as &T, f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self as &T, f)
}
}
pub(crate) struct UserDataWrapped<T> {
pub(crate) data: *mut T,
pub(crate) enum UserDataWrapped<T> {
Default(Box<T>),
#[cfg(feature = "serialize")]
ser: *mut dyn erased_serde::Serialize,
Serializable(Box<dyn erased_serde::Serialize>),
}
impl<T> UserDataWrapped<T> {
#[inline]
fn new(data: T) -> Self {
UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
}
UserDataWrapped::Default(Box::new(data))
}
#[cfg(feature = "serialize")]
#[inline]
fn new_ser(data: T) -> Self
where
T: 'static + Serialize,
{
let data_raw = Box::into_raw(Box::new(data));
UserDataWrapped {
data: data_raw,
ser: data_raw,
}
UserDataWrapped::Serializable(Box::new(data))
}
}
impl<T> Drop for UserDataWrapped<T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.data));
#[inline]
fn into_inner(self) -> T {
match self {
Self::Default(data) => *data,
#[cfg(feature = "serialize")]
if self.data as *mut () != self.ser as *mut () {
drop(Box::from_raw(self.ser));
}
Self::Serializable(data) => unsafe { *Box::from_raw(Box::into_raw(data) as *mut T) },
}
}
}
@@ -747,14 +667,28 @@ impl<T> Drop for UserDataWrapped<T> {
impl<T> Deref for UserDataWrapped<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &*self.data }
match self {
Self::Default(data) => data,
#[cfg(feature = "serialize")]
Self::Serializable(data) => unsafe {
&*(data.as_ref() as *const _ as *const Self::Target)
},
}
}
}
impl<T> DerefMut for UserDataWrapped<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.data }
match self {
Self::Default(data) => data,
#[cfg(feature = "serialize")]
Self::Serializable(data) => unsafe {
&mut *(data.as_mut() as *mut _ as *mut Self::Target)
},
}
}
}
@@ -784,9 +718,9 @@ impl Serialize for UserDataSerializeError {
/// This API should only be used when necessary. Implementing [`UserData`] already allows defining
/// methods which check the type and acquire a borrow behind the scenes.
///
/// [`UserData`]: trait.UserData.html
/// [`is`]: #method.is
/// [`borrow`]: #method.borrow
/// [`UserData`]: crate::UserData
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug)]
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
@@ -806,7 +740,8 @@ impl<'lua> AnyUserData<'lua> {
///
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow<T: 'static + UserData>(&self) -> Result<UserDataRef<T>> {
#[inline]
pub fn borrow<T: 'static + UserData>(&self) -> Result<Ref<T>> {
self.inspect(|cell| cell.try_borrow())
}
@@ -816,10 +751,40 @@ impl<'lua> AnyUserData<'lua> {
///
/// Returns a `UserDataBorrowMutError` if the userdata cannot be mutably borrowed.
/// Returns a `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<UserDataRefMut<T>> {
#[inline]
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<RefMut<T>> {
self.inspect(|cell| cell.try_borrow_mut())
}
/// Takes out the value of `UserData` and sets the special "destructed" metatable that prevents
/// any further operations with this userdata.
#[doc(hidden)]
pub fn take<T: 'static + UserData>(&self) -> Result<T> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 2)?;
let type_id = lua.push_userdata_ref(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
// Try to borrow userdata exclusively
let _ = (*get_userdata::<UserDataCell<T>>(lua.state, -1)).try_borrow_mut()?;
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushnil(lua.state);
#[cfg(any(feature = "lua51", feature = "luajit"))]
protect_lua!(lua.state, 0, 1, fn(state) ffi::lua_newtable(state))?;
ffi::lua_setuservalue(lua.state, -2);
Ok(take_userdata::<UserDataCell<T>>(lua.state).into_inner())
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
/// Sets an associated value to this `AnyUserData`.
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`get_user_value`].
@@ -841,7 +806,7 @@ impl<'lua> AnyUserData<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0, false)?;
lua.push_userdata_ref(&self.0)?;
lua.push_value(v)?;
ffi::lua_setuservalue(lua.state, -2);
@@ -860,7 +825,7 @@ impl<'lua> AnyUserData<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0, false)?;
lua.push_userdata_ref(&self.0)?;
ffi::lua_getuservalue(lua.state, -1);
lua.pop_value()
};
@@ -880,7 +845,7 @@ impl<'lua> AnyUserData<'lua> {
///
/// For `T: UserData + 'static` returned metatable is shared among all instances of type `T`.
///
/// [`UserDataMetatable`]: struct.UserDataMetatable.html
/// [`UserDataMetatable`]: crate::UserDataMetatable
pub fn get_metatable(&self) -> Result<UserDataMetatable<'lua>> {
self.get_raw_metatable().map(UserDataMetatable)
}
@@ -891,7 +856,7 @@ impl<'lua> AnyUserData<'lua> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
lua.push_userdata_ref(&self.0, false)?;
lua.push_userdata_ref(&self.0)?;
ffi::lua_getmetatable(lua.state, -1); // Checked that non-empty on the previous call
Ok(Table(lua.pop_ref()))
}
@@ -918,25 +883,6 @@ impl<'lua> AnyUserData<'lua> {
Ok(false)
}
pub(crate) fn type_id(&self) -> Result<TypeId> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
// Push userdata with metatable
lua.push_userdata_ref(&self.0, true)?;
// Get the special `__mlua_type_id`
push_string(lua.state, "__mlua_type_id")?;
if ffi::lua_rawget(lua.state, -2) != ffi::LUA_TUSERDATA {
return Err(Error::UserDataTypeMismatch);
}
Ok(*(ffi::lua_touserdata(lua.state, -1) as *const TypeId))
}
}
fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R>
where
T: 'static + UserData,
@@ -945,25 +891,14 @@ impl<'lua> AnyUserData<'lua> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
check_stack(lua.state, 2)?;
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
return Err(Error::UserDataTypeMismatch);
}
lua.push_userdata_metatable::<T>()?;
if ffi::lua_rawequal(lua.state, -1, -2) == 0 {
// Maybe UserData destructed?
ffi::lua_pop(lua.state, 1);
get_destructed_userdata_metatable(lua.state);
if ffi::lua_rawequal(lua.state, -1, -2) == 1 {
Err(Error::UserDataDestructed)
} else {
Err(Error::UserDataTypeMismatch)
let type_id = lua.push_userdata_ref(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
func(&*get_userdata::<UserDataCell<T>>(lua.state, -1))
}
} else {
func(&*get_userdata::<UserDataCell<T>>(lua.state, -3))
_ => Err(Error::UserDataTypeMismatch),
}
}
}
@@ -1019,7 +954,7 @@ impl<'lua> UserDataMetatable<'lua> {
///
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
///
/// [`Result`]: type.Result.html
/// [`Result`]: crate::Result
pub fn pairs<V: FromLua<'lua>>(self) -> UserDataMetatablePairs<'lua, V> {
UserDataMetatablePairs(self.0.pairs())
}
@@ -1031,8 +966,8 @@ impl<'lua> UserDataMetatable<'lua> {
///
/// This struct is created by the [`UserDataMetatable::pairs`] method.
///
/// [`UserData`]: trait.UserData.html
/// [`UserDataMetatable::pairs`]: struct.UserDataMetatable.html#method.pairs
/// [`UserData`]: crate::UserData
/// [`UserDataMetatable::pairs`]: crate::UserDataMetatable::method.pairs
pub struct UserDataMetatablePairs<'lua, V>(TablePairs<'lua, StdString, V>);
impl<'lua, V> Iterator for UserDataMetatablePairs<'lua, V>
@@ -1062,15 +997,19 @@ impl<'lua> Serialize for AnyUserData<'lua> {
where
S: Serializer,
{
unsafe {
let lua = self.0.lua;
let lua = self.0.lua;
let data = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3).map_err(ser::Error::custom)?;
lua.push_userdata_ref(&self.0, false)
.map_err(ser::Error::custom)?;
lua.push_userdata_ref(&self.0).map_err(ser::Error::custom)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
ud.serialize(serializer)
ud.0.try_borrow()
.map_err(|_| ser::Error::custom(Error::UserDataBorrowError))?
};
match &*data {
UserDataWrapped::Default(_) => UserDataSerializeError.serialize(serializer),
UserDataWrapped::Serializable(ser) => ser.serialize(serializer),
}
}
}
+182 -166
View File
@@ -4,7 +4,7 @@ use std::error::Error as StdError;
use std::fmt::Write;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use std::{mem, ptr, slice};
use once_cell::sync::Lazy;
@@ -12,13 +12,17 @@ use once_cell::sync::Lazy;
use crate::error::{Error, Result};
use crate::ffi;
static METATABLE_CACHE: Lazy<Mutex<HashMap<TypeId, u8>>> = Lazy::new(|| {
// The capacity must(!) be greater than number of stored keys
Mutex::new(HashMap::with_capacity(32))
static METATABLE_CACHE: Lazy<HashMap<TypeId, u8>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(32);
crate::lua::init_metatable_cache(&mut map);
map.insert(TypeId::of::<WrappedFailure>(), 0);
map.insert(TypeId::of::<String>(), 0);
map
});
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
// panic with an internal error message.
#[inline]
pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) {
// TODO: This should only be triggered when there is a logic error in `mlua`. In the future,
// when there is a way to be confident about stack safety and test it, this could be enabled
@@ -30,6 +34,7 @@ pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) {
}
// Checks that Lua has enough free stack space and returns `Error::StackError` on failure.
#[inline]
pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<()> {
if ffi::lua_checkstack(state, amount) == 0 {
Err(Error::StackError)
@@ -48,6 +53,7 @@ impl StackGuard {
// Creates a StackGuard instance with record of the stack size, and on Drop will check the
// stack size and drop any extra elements. If the stack size at the end is *smaller* than at
// the beginning, this is considered a fatal logic error and will result in a panic.
#[inline]
pub unsafe fn new(state: *mut ffi::lua_State) -> StackGuard {
StackGuard {
state,
@@ -57,6 +63,7 @@ impl StackGuard {
}
// Similar to `new`, but checks and keeps `extra` elements from top of the stack on Drop.
#[inline]
pub unsafe fn new_extra(state: *mut ffi::lua_State, extra: c_int) -> StackGuard {
StackGuard {
state,
@@ -83,6 +90,35 @@ impl Drop for StackGuard {
}
}
// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is
// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be lonjmping,
// should not contain any values that implements Drop.
// Internally uses 2 extra stack spaces, and does not call checkstack.
pub unsafe fn protect_lua_call(
state: *mut ffi::lua_State,
nargs: c_int,
f: unsafe extern "C" fn(*mut ffi::lua_State) -> c_int,
) -> Result<()> {
let stack_start = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, f);
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2);
}
let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1);
ffi::lua_remove(state, stack_start + 1);
if ret == ffi::LUA_OK {
Ok(())
} else {
Err(pop_error(state, ret))
}
}
// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
// limited lua stack. `nargs` and `nresults` are similar to the parameters of `lua_pcall`, but the
@@ -90,7 +126,7 @@ impl Drop for StackGuard {
// values are assumed to match the `nresults` param. Provided function must *not* panic, and since it
// will generally be lonjmping, should not contain any values that implements Drop.
// Internally uses 3 extra stack spaces, and does not call checkstack.
pub unsafe fn protect_lua<F, R>(
pub unsafe fn protect_lua_closure<F, R>(
state: *mut ffi::lua_State,
nargs: c_int,
nresults: c_int,
@@ -167,84 +203,92 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
"pop_error called with non-error return code"
);
if let Some(err) = get_wrapped_error(state, -1).as_ref() {
ffi::lua_pop(state, 1);
err.clone()
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_mut() {
if let Some(p) = (*panic).0.take() {
resume_unwind(p);
} else {
Error::PreviouslyResumedPanic
match get_gc_userdata::<WrappedFailure>(state, -1).as_mut() {
Some(WrappedFailure::Error(err)) => {
ffi::lua_pop(state, 1);
err.clone()
}
} else {
let err_string = to_string(state, -1);
ffi::lua_pop(state, 1);
Some(WrappedFailure::Panic(panic)) => {
if let Some(p) = panic.take() {
resume_unwind(p);
} else {
Error::PreviouslyResumedPanic
}
}
_ => {
let err_string = to_string(state, -1);
ffi::lua_pop(state, 1);
match err_code {
ffi::LUA_ERRRUN => Error::RuntimeError(err_string),
ffi::LUA_ERRSYNTAX => {
Error::SyntaxError {
// This seems terrible, but as far as I can tell, this is exactly what the
// stock Lua REPL does.
incomplete_input: err_string.ends_with("<eof>")
|| err_string.ends_with("'<eof>'"),
message: err_string,
match err_code {
ffi::LUA_ERRRUN => Error::RuntimeError(err_string),
ffi::LUA_ERRSYNTAX => {
Error::SyntaxError {
// This seems terrible, but as far as I can tell, this is exactly what the
// stock Lua REPL does.
incomplete_input: err_string.ends_with("<eof>")
|| err_string.ends_with("'<eof>'"),
message: err_string,
}
}
ffi::LUA_ERRERR => {
// This error is raised when the error handler raises an error too many times
// recursively, and continuing to trigger the error handler would cause a stack
// overflow. It is not very useful to differentiate between this and "ordinary"
// runtime errors, so we handle them the same way.
Error::RuntimeError(err_string)
}
ffi::LUA_ERRMEM => Error::MemoryError(err_string),
#[cfg(any(feature = "lua53", feature = "lua52"))]
ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string),
_ => mlua_panic!("unrecognized lua error code"),
}
ffi::LUA_ERRERR => {
// This error is raised when the error handler raises an error too many times
// recursively, and continuing to trigger the error handler would cause a stack
// overflow. It is not very useful to differentiate between this and "ordinary"
// runtime errors, so we handle them the same way.
Error::RuntimeError(err_string)
}
ffi::LUA_ERRMEM => Error::MemoryError(err_string),
#[cfg(any(feature = "lua53", feature = "lua52"))]
ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string),
_ => mlua_panic!("unrecognized lua error code"),
}
}
}
// Uses 3 stack spaces
// 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,
) -> Result<()> {
let s = s.as_ref();
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());
})
}
// Uses 3 stack spaces
// Uses 3 stack spaces, does not call checkstack.
#[inline]
pub unsafe fn push_table(state: *mut ffi::lua_State, narr: c_int, nrec: c_int) -> Result<()> {
protect_lua(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
}
// Uses 4 stack spaces
// Uses 4 stack spaces, does not call checkstack.
pub unsafe fn rawset_field<S>(state: *mut ffi::lua_State, table: c_int, field: &S) -> Result<()>
where
S: AsRef<[u8]> + ?Sized,
{
let field = field.as_ref();
ffi::lua_pushvalue(state, table);
protect_lua(state, 2, 0, |state| {
protect_lua!(state, 2, 0, |state| {
ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len());
ffi::lua_rotate(state, -3, 2);
ffi::lua_rawset(state, -3)
ffi::lua_rawset(state, -3);
})
}
// Internally uses 3 stack spaces, does not call checkstack.
#[inline]
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
let ud = protect_lua(state, 0, 1, |state| {
let ud = protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
})?;
ptr::write(ud, t);
Ok(())
}
#[inline]
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
mlua_debug_assert!(!ud.is_null(), "userdata pointer is null");
@@ -271,7 +315,7 @@ pub unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
// Internally uses 3 stack spaces, does not call checkstack.
pub unsafe fn push_gc_userdata<T: Any>(state: *mut ffi::lua_State, t: T) -> Result<()> {
push_userdata(state, t)?;
get_gc_metatable_for::<T>(state);
get_gc_metatable::<T>(state);
ffi::lua_setmetatable(state, -2);
Ok(())
}
@@ -282,7 +326,7 @@ pub unsafe fn get_gc_userdata<T: Any>(state: *mut ffi::lua_State, index: c_int)
if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
return ptr::null_mut();
}
get_gc_metatable_for::<T>(state);
get_gc_metatable::<T>(state);
let res = ffi::lua_rawequal(state, -1, -2);
ffi::lua_pop(state, 2);
if res == 0 {
@@ -408,8 +452,8 @@ pub unsafe fn init_userdata_metatable<T>(
ffi::lua_pushnil(state);
}
}
protect_lua(state, 3, 1, |state| {
ffi::lua_pushcclosure(state, meta_index_impl, 3)
protect_lua!(state, 3, 1, fn(state) {
ffi::lua_pushcclosure(state, meta_index_impl, 3);
})?;
}
_ => mlua_panic!("improper __index type {}", index_type),
@@ -424,8 +468,8 @@ pub unsafe fn init_userdata_metatable<T>(
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
ffi::lua_pushvalue(state, field_setters);
protect_lua(state, 2, 1, |state| {
ffi::lua_pushcclosure(state, meta_newindex_impl, 2)
protect_lua!(state, 2, 1, fn(state) {
ffi::lua_pushcclosure(state, meta_newindex_impl, 2);
})?;
}
_ => mlua_panic!("improper __newindex type {}", newindex_type),
@@ -446,11 +490,10 @@ pub unsafe fn init_userdata_metatable<T>(
}
pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |_| {
check_stack(state, 1)?;
take_userdata::<T>(state);
Ok(0)
})
// It's probably NOT a good idea to catch Rust panics in finalizer
// Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
take_userdata::<T>(state);
0
}
// In the context of a lua callback, this will call the given function and if the given function
@@ -480,10 +523,7 @@ where
// We cannot shadow Rust errors with Lua ones, we pre-allocate enough memory
// to store a wrapped error or panic *before* we proceed.
let ud = ffi::lua_newuserdata(
state,
mem::size_of::<WrappedError>().max(mem::size_of::<WrappedPanic>()),
);
let ud = ffi::lua_newuserdata(state, mem::size_of::<WrappedFailure>());
ffi::lua_rotate(state, 1, 1);
match catch_unwind(AssertUnwindSafe(|| f(nargs))) {
@@ -494,9 +534,9 @@ where
Ok(Err(err)) => {
ffi::lua_settop(state, 1);
let wrapped_error = ud as *mut WrappedError;
ptr::write(wrapped_error, WrappedError(err));
get_gc_metatable_for::<WrappedError>(state);
let wrapped_error = ud as *mut WrappedFailure;
ptr::write(wrapped_error, WrappedFailure::Error(err));
get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
// Convert to CallbackError and attach traceback
@@ -508,15 +548,17 @@ where
} else {
"<not enough stack space for traceback>".to_string()
};
let cause = Arc::new((*wrapped_error).0.clone());
(*wrapped_error).0 = Error::CallbackError { traceback, cause };
if let WrappedFailure::Error(ref mut err) = *wrapped_error {
let cause = Arc::new(err.clone());
*err = Error::CallbackError { traceback, cause };
}
ffi::lua_error(state)
}
Err(p) => {
ffi::lua_settop(state, 1);
ptr::write(ud as *mut WrappedPanic, WrappedPanic(Some(p)));
get_gc_metatable_for::<WrappedPanic>(state);
ptr::write(ud as *mut WrappedFailure, WrappedFailure::Panic(Some(p)));
get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
@@ -530,9 +572,7 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
return 1;
}
if get_gc_userdata::<WrappedError>(state, -1).is_null()
&& get_gc_userdata::<WrappedPanic>(state, -1).is_null()
{
if get_gc_userdata::<WrappedFailure>(state, -1).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, state, s, 1);
@@ -558,7 +598,9 @@ pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
ffi::lua_insert(state, 1);
ffi::lua_gettop(state)
} else {
if !get_gc_userdata::<WrappedPanic>(state, -1).is_null() {
if let Some(WrappedFailure::Panic(_)) =
get_gc_userdata::<WrappedFailure>(state, -1).as_ref()
{
ffi::lua_error(state);
}
ffi::lua_pushboolean(state, 0);
@@ -572,7 +614,9 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C" fn xpcall_msgh(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
if !get_gc_userdata::<WrappedPanic>(state, -1).is_null() {
if let Some(WrappedFailure::Panic(_)) =
get_gc_userdata::<WrappedFailure>(state, -1).as_ref()
{
1
} else {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
@@ -600,7 +644,9 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
ffi::lua_insert(state, 2);
ffi::lua_gettop(state) - 1
} else {
if !get_gc_userdata::<WrappedPanic>(state, -1).is_null() {
if let Some(WrappedFailure::Panic(_)) =
get_gc_userdata::<WrappedFailure>(state, -1).as_ref()
{
ffi::lua_error(state);
}
ffi::lua_pushboolean(state, 0);
@@ -632,42 +678,14 @@ pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua
}
}
// Pushes a WrappedError to the top of the stack.
// Uses 3 stack spaces and does not call checkstack.
pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) -> Result<()> {
push_gc_userdata::<WrappedError>(state, WrappedError(err))
}
// Checks if the value at the given index is a WrappedError, and if it is returns a pointer to it,
// otherwise returns null.
// Uses 2 stack spaces and does not call checkstack.
pub unsafe fn get_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> *const Error {
let ud = get_gc_userdata::<WrappedError>(state, index);
if ud.is_null() {
return ptr::null();
}
&(*ud).0
}
// Initialize the internal (with __gc method) metatable for a type T.
// Uses 6 stack spaces and calls checkstack.
pub unsafe fn init_gc_metatable_for<T: Any>(
pub unsafe fn init_gc_metatable<T: Any>(
state: *mut ffi::lua_State,
customize_fn: Option<fn(*mut ffi::lua_State) -> Result<()>>,
) -> Result<()> {
check_stack(state, 6)?;
let type_id = TypeId::of::<T>();
let ref_addr = {
let mut mt_cache = mlua_expect!(METATABLE_CACHE.lock(), "cannot lock metatable cache");
mlua_assert!(
mt_cache.capacity() - mt_cache.len() > 0,
"out of metatable cache capacity"
);
mt_cache.insert(type_id, 0);
&mt_cache[&type_id] as *const u8
};
push_table(state, 0, 3)?;
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
@@ -680,24 +698,23 @@ pub unsafe fn init_gc_metatable_for<T: Any>(
f(state)?;
}
protect_lua(state, 1, 0, |state| {
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *mut c_void)
let type_id = TypeId::of::<T>();
let ref_addr = &METATABLE_CACHE[&type_id] as *const u8;
protect_lua!(state, 1, 0, |state| {
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *const c_void);
})?;
Ok(())
}
pub unsafe fn get_gc_metatable_for<T: Any>(state: *mut ffi::lua_State) {
pub unsafe fn get_gc_metatable<T: Any>(state: *mut ffi::lua_State) {
let type_id = TypeId::of::<T>();
let ref_addr = {
let mt_cache = mlua_expect!(METATABLE_CACHE.lock(), "cannot lock metatable cache");
mlua_expect!(mt_cache.get(&type_id), "gc metatable does not exist") as *const u8
};
let ref_addr =
mlua_expect!(METATABLE_CACHE.get(&type_id), "gc metatable does not exist") as *const u8;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *const c_void);
}
// Initialize the error, panic, and destructed userdata metatables.
// Returns address of WrappedError and WrappedPanic metatables in Lua registry.
pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
check_stack(state, 7)?;
@@ -707,63 +724,67 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
callback_error(state, |_| {
check_stack(state, 3)?;
let err_buf = if let Some(error) = get_wrapped_error(state, -1).as_ref() {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
ffi::lua_pop(state, 2);
let err_buf = match get_gc_userdata::<WrappedFailure>(state, -1).as_ref() {
Some(WrappedFailure::Error(error)) => {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
ffi::lua_pop(state, 2);
(*err_buf).clear();
// Depending on how the API is used and what error types scripts are given, it may
// be possible to make this consume arbitrary amounts of memory (for example, some
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error);
// Find first two sources that caused the error
let mut source1 = error.source();
let mut source0 = source1.and_then(|s| s.source());
while let Some(source) = source0.and_then(|s| s.source()) {
source1 = source0;
source0 = Some(source);
}
match (source1, source0) {
(_, Some(error0)) if error0.to_string().contains("\nstack traceback:\n") => {
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0);
(*err_buf).clear();
// Depending on how the API is used and what error types scripts are given, it may
// be possible to make this consume arbitrary amounts of memory (for example, some
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error);
// Find first two sources that caused the error
let mut source1 = error.source();
let mut source0 = source1.and_then(|s| s.source());
while let Some(source) = source0.and_then(|s| s.source()) {
source1 = source0;
source0 = Some(source);
}
(Some(error1), Some(error0)) => {
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0);
let s = error1.to_string();
if let Some(traceback) = s.splitn(2, "\nstack traceback:\n").nth(1) {
let _ = write!(&mut (*err_buf), "\nstack traceback:\n{}", traceback);
match (source1, source0) {
(_, Some(error0))
if error0.to_string().contains("\nstack traceback:\n") =>
{
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0);
}
(Some(error1), Some(error0)) => {
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0);
let s = error1.to_string();
if let Some(traceback) = s.split_once("\nstack traceback:\n") {
let _ =
write!(&mut (*err_buf), "\nstack traceback:\n{}", traceback.1);
}
}
(Some(error1), None) => {
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error1);
}
_ => {}
}
(Some(error1), None) => {
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error1);
}
_ => {}
Ok(err_buf)
}
Ok(err_buf)
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_ref() {
if let Some(ref p) = (*panic).0 {
Some(WrappedFailure::Panic(Some(ref panic))) => {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
(*err_buf).clear();
ffi::lua_pop(state, 2);
if let Some(msg) = p.downcast_ref::<&str>() {
if let Some(msg) = panic.downcast_ref::<&str>() {
let _ = write!(&mut (*err_buf), "{}", msg);
} else if let Some(msg) = p.downcast_ref::<String>() {
} else if let Some(msg) = panic.downcast_ref::<String>() {
let _ = write!(&mut (*err_buf), "{}", msg);
} else {
let _ = write!(&mut (*err_buf), "<panic>");
};
Ok(err_buf)
} else {
Err(Error::PreviouslyResumedPanic)
}
} else {
// I'm not sure whether this is possible to trigger without bugs in mlua?
Err(Error::UserDataTypeMismatch)
Some(WrappedFailure::Panic(None)) => Err(Error::PreviouslyResumedPanic),
_ => {
// I'm not sure whether this is possible to trigger without bugs in mlua?
Err(Error::UserDataTypeMismatch)
}
}?;
push_string(state, &*err_buf)?;
@@ -773,15 +794,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
})
}
init_gc_metatable_for::<WrappedError>(
state,
Some(|state| {
ffi::lua_pushcfunction(state, error_tostring);
rawset_field(state, -2, "__tostring")
}),
)?;
init_gc_metatable_for::<WrappedPanic>(
init_gc_metatable::<WrappedFailure>(
state,
Some(|state| {
ffi::lua_pushcfunction(state, error_tostring);
@@ -792,6 +805,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Create destructed userdata metatable
unsafe extern "C" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
// TODO: Consider changing error to UserDataDestructed in v0.7
callback_error(state, |_| Err(Error::CallbackDestructed))
}
@@ -840,24 +854,26 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
}
ffi::lua_pop(state, 1);
let destructed_metatable_key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void;
protect_lua(state, 1, 0, |state| {
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, destructed_metatable_key)
protect_lua!(state, 1, 0, fn(state) {
let destructed_mt_key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void;
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, destructed_mt_key);
})?;
// Create error print buffer
init_gc_metatable_for::<String>(state, None)?;
init_gc_metatable::<String>(state, None)?;
push_gc_userdata(state, String::new())?;
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
protect_lua(state, 1, 0, |state| {
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key)
protect_lua!(state, 1, 0, fn(state) {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
})?;
Ok(())
}
pub(crate) struct WrappedError(pub Error);
pub(crate) struct WrappedPanic(pub Option<Box<dyn Any + Send + 'static>>);
pub(crate) enum WrappedFailure {
Error(Error),
Panic(Option<Box<dyn Any + Send + 'static>>),
}
// Converts the given lua value to a string in a reasonable format without causing a Lua error or
// panicking.
+14
View File
@@ -49,6 +49,7 @@ pub enum Value<'lua> {
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Error),
}
pub use self::Value::Nil;
impl<'lua> Value<'lua> {
@@ -157,18 +158,21 @@ pub struct MultiValue<'lua>(Vec<Value<'lua>>);
impl<'lua> MultiValue<'lua> {
/// Creates an empty `MultiValue` containing no values.
#[inline]
pub fn new() -> MultiValue<'lua> {
MultiValue(Vec::new())
}
}
impl<'lua> Default for MultiValue<'lua> {
#[inline]
fn default() -> MultiValue<'lua> {
MultiValue::new()
}
}
impl<'lua> FromIterator<Value<'lua>> for MultiValue<'lua> {
#[inline]
fn from_iter<I: IntoIterator<Item = Value<'lua>>>(iter: I) -> Self {
MultiValue::from_vec(Vec::from_iter(iter))
}
@@ -178,6 +182,7 @@ impl<'lua> IntoIterator for MultiValue<'lua> {
type Item = Value<'lua>;
type IntoIter = iter::Rev<vec::IntoIter<Value<'lua>>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter().rev()
}
@@ -187,43 +192,52 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
type Item = &'a Value<'lua>;
type IntoIter = iter::Rev<slice::Iter<'a, Value<'lua>>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter().rev()
}
}
impl<'lua> MultiValue<'lua> {
#[inline]
pub fn from_vec(mut v: Vec<Value<'lua>>) -> MultiValue<'lua> {
v.reverse();
MultiValue(v)
}
#[inline]
pub fn into_vec(self) -> Vec<Value<'lua>> {
let mut v = self.0;
v.reverse();
v
}
#[inline]
pub(crate) fn reserve(&mut self, size: usize) {
self.0.reserve(size);
}
#[inline]
pub(crate) fn push_front(&mut self, value: Value<'lua>) {
self.0.push(value);
}
#[inline]
pub(crate) fn pop_front(&mut self) -> Option<Value<'lua>> {
self.0.pop()
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.len() == 0
}
#[inline]
pub fn iter(&self) -> iter::Rev<slice::Iter<Value<'lua>>> {
self.0.iter().rev()
}
+1 -1
View File
@@ -4,7 +4,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test`, w
9 | let _ = lua.create_function(|_, ()| -> Result<i32> {
| ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test`
10 | Ok(test.0)
| ---- `test` is borrowed here
| ------ `test` is borrowed here
|
note: function requires argument type to outlive `'static`
--> $DIR/function_borrow.rs:9:13
+19
View File
@@ -14,3 +14,22 @@ error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a re
= 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]`
error[E0277]: the type `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<UnsafeCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<UnsafeCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `Arc<UnsafeCell<mlua::lua::ExtraData>>`
= 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]`
+21
View File
@@ -16,3 +16,24 @@ error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a re
= 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]`
error[E0277]: the type `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `alloc::sync::ArcInner<UnsafeCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<UnsafeCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `Arc<UnsafeCell<mlua::lua::ExtraData>>`
= 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 `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]`
+1 -1
View File
@@ -7,7 +7,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test`, w
14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test`
15 | test.field = 42;
| ---- `test` is borrowed here
| ---------- `test` is borrowed here
|
note: function requires argument type to outlive `'1`
--> $DIR/scope_invariance.rs:13:13
+1 -1
View File
@@ -2,7 +2,7 @@ use mlua::{Lua, UserData};
fn main() {
struct MyUserData<'a>(&'a mut i32);
impl<'a> UserData for MyUserData<'a> {};
impl<'a> UserData for MyUserData<'a> {}
let mut i = 1;
+1 -1
View File
@@ -3,7 +3,7 @@ use mlua::{Lua, UserData};
fn main() {
// Should not allow userdata borrow to outlive lifetime of AnyUserData handle
struct MyUserData<'a>(&'a i32);
impl<'a> UserData for MyUserData<'a> {};
impl<'a> UserData for MyUserData<'a> {}
let igood = 1;
+14 -8
View File
@@ -12,7 +12,7 @@ fn test_conv_vec() -> Result<()> {
let v = vec![1, 2, 3];
lua.globals().set("v", v.clone())?;
let v2: Vec<i32> = lua.globals().get("v")?;
assert!(v == v2);
assert_eq!(v, v2);
Ok(())
}
@@ -24,7 +24,7 @@ fn test_conv_hashmap() -> Result<()> {
let map = hashmap! {"hello".to_string() => "world".to_string()};
lua.globals().set("map", map.clone())?;
let map2: HashMap<String, String> = lua.globals().get("map")?;
assert!(map == map2);
assert_eq!(map, map2);
Ok(())
}
@@ -36,7 +36,10 @@ fn test_conv_hashset() -> Result<()> {
let set = hashset! {"hello".to_string(), "world".to_string()};
lua.globals().set("set", set.clone())?;
let set2: HashSet<String> = lua.globals().get("set")?;
assert!(set == set2);
assert_eq!(set, set2);
let set3 = lua.load(r#"{"a", "b", "c"}"#).eval::<HashSet<String>>()?;
assert_eq!(set3, hashset! { "a".into(), "b".into(), "c".into() });
Ok(())
}
@@ -48,7 +51,7 @@ fn test_conv_btreemap() -> Result<()> {
let map = btreemap! {"hello".to_string() => "world".to_string()};
lua.globals().set("map", map.clone())?;
let map2: BTreeMap<String, String> = lua.globals().get("map")?;
assert!(map == map2);
assert_eq!(map, map2);
Ok(())
}
@@ -60,7 +63,10 @@ fn test_conv_btreeset() -> Result<()> {
let set = btreeset! {"hello".to_string(), "world".to_string()};
lua.globals().set("set", set.clone())?;
let set2: BTreeSet<String> = lua.globals().get("set")?;
assert!(set == set2);
assert_eq!(set, set2);
let set3 = lua.load(r#"{"a", "b", "c"}"#).eval::<BTreeSet<String>>()?;
assert_eq!(set3, btreeset! { "a".into(), "b".into(), "c".into() });
Ok(())
}
@@ -89,7 +95,7 @@ fn test_conv_cow() -> Result<()> {
let s = Cow::from("hello");
lua.globals().set("s", s.clone())?;
let s2: String = lua.globals().get("s")?;
assert!(s == s2);
assert_eq!(s, s2);
Ok(())
}
@@ -101,7 +107,7 @@ fn test_conv_boxed_str() -> Result<()> {
let s = String::from("hello").into_boxed_str();
lua.globals().set("s", s.clone())?;
let s2: Box<str> = lua.globals().get("s")?;
assert!(s == s2);
assert_eq!(s, s2);
Ok(())
}
@@ -113,7 +119,7 @@ fn test_conv_boxed_slice() -> Result<()> {
let v = vec![1, 2, 3].into_boxed_slice();
lua.globals().set("v", v.clone())?;
let v2: Box<[i32]> = lua.globals().get("v")?;
assert!(v == v2);
assert_eq!(v, v2);
Ok(())
}
+17
View File
@@ -76,6 +76,23 @@ fn test_rust_function() -> Result<()> {
Ok(())
}
#[test]
fn test_c_function() -> Result<()> {
let lua = Lua::new();
unsafe extern "C" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int {
let lua = Lua::init_from_ptr(state);
lua.globals().set("c_function", true).unwrap();
0
}
let func = unsafe { lua.create_c_function(c_function)? };
func.call(())?;
assert_eq!(lua.globals().get::<_, bool>("c_function")?, true);
Ok(())
}
#[test]
fn test_dump() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
+48 -64
View File
@@ -3,7 +3,20 @@ use std::ops::Deref;
use std::str;
use std::sync::{Arc, Mutex};
use mlua::{Error, HookTriggers, Lua, Result, Value};
use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, Value};
#[test]
fn test_hook_triggers_bitor() {
let trigger = HookTriggers::on_calls()
| HookTriggers::on_returns()
| HookTriggers::every_line()
| HookTriggers::every_nth_instruction(5);
assert!(trigger.on_calls);
assert!(trigger.on_returns);
assert!(trigger.every_line);
assert_eq!(trigger.every_nth_instruction, Some(5));
}
#[test]
fn test_line_counts() -> Result<()> {
@@ -11,16 +24,11 @@ fn test_line_counts() -> Result<()> {
let hook_output = output.clone();
let lua = Lua::new();
lua.set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
move |_lua, debug| {
hook_output.lock().unwrap().push(debug.curr_line());
Ok(())
},
)?;
lua.set_hook(HookTriggers::every_line(), move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(())
})?;
lua.load(
r#"
local x = 2 + 3
@@ -48,20 +56,15 @@ fn test_function_calls() -> Result<()> {
let hook_output = output.clone();
let lua = Lua::new();
lua.set_hook(
HookTriggers {
on_calls: true,
..Default::default()
},
move |_lua, debug| {
let names = debug.names();
let source = debug.source();
let name = names.name.map(|s| str::from_utf8(s).unwrap().to_owned());
let what = source.what.map(|s| str::from_utf8(s).unwrap().to_owned());
hook_output.lock().unwrap().push((name, what));
Ok(())
},
)?;
lua.set_hook(HookTriggers::on_calls(), move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Call);
let names = debug.names();
let source = debug.source();
let name = names.name.map(|s| str::from_utf8(s).unwrap().to_owned());
let what = source.what.map(|s| str::from_utf8(s).unwrap().to_owned());
hook_output.lock().unwrap().push((name, what));
Ok(())
})?;
lua.load(
r#"
@@ -97,17 +100,12 @@ fn test_function_calls() -> Result<()> {
#[test]
fn test_error_within_hook() -> Result<()> {
let lua = Lua::new();
lua.set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
|_lua, _debug| {
Err(Error::RuntimeError(
"Something happened in there!".to_string(),
))
},
)?;
lua.set_hook(HookTriggers::every_line(), |_lua, _debug| {
Err(Error::RuntimeError(
"Something happened in there!".to_string(),
))
})?;
let err = lua
.load("x = 1")
@@ -135,11 +133,9 @@ fn test_limit_execution_instructions() -> Result<()> {
lua.load("jit.off()").exec()?;
lua.set_hook(
HookTriggers {
every_nth_instruction: Some(30),
..Default::default()
},
move |_lua, _debug| {
HookTriggers::every_nth_instruction(30),
move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Count);
max_instructions -= 30;
if max_instructions < 0 {
Err(Error::RuntimeError("time's up".to_string()))
@@ -168,17 +164,11 @@ fn test_limit_execution_instructions() -> Result<()> {
fn test_hook_removal() -> Result<()> {
let lua = Lua::new();
lua.set_hook(
HookTriggers {
every_nth_instruction: Some(1),
..Default::default()
},
|_lua, _debug| {
Err(Error::RuntimeError(
"this hook should've been removed by this time".to_string(),
))
},
)?;
lua.set_hook(HookTriggers::every_nth_instruction(1), |_lua, _debug| {
Err(Error::RuntimeError(
"this hook should've been removed by this time".to_string(),
))
})?;
assert!(lua.load("local x = 1").exec().is_err());
lua.remove_hook();
@@ -198,19 +188,14 @@ fn test_hook_swap_within_hook() -> Result<()> {
});
TL_LUA.with(|tl| {
tl.borrow().as_ref().unwrap().set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
move |lua, _debug| {
tl.borrow()
.as_ref()
.unwrap()
.set_hook(HookTriggers::every_line(), move |lua, _debug| {
lua.globals().set("ok", 1i64)?;
TL_LUA.with(|tl| {
tl.borrow().as_ref().unwrap().set_hook(
HookTriggers {
every_line: true,
..Default::default()
},
HookTriggers::every_line(),
move |lua, _debug| {
lua.load(
r#"
@@ -228,8 +213,7 @@ fn test_hook_swap_within_hook() -> Result<()> {
},
)
})
},
)
})
})?;
TL_LUA.with(|tl| {
+3
View File
@@ -1,5 +1,8 @@
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-args=-rdynamic"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-args=-rdynamic"]
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-args=-rdynamic"]
+87 -26
View File
@@ -2,7 +2,10 @@
use std::collections::HashMap;
use mlua::{Error, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData, Value};
use mlua::{
DeserializeOptions, Error, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData,
Value,
};
use serde::{Deserialize, Serialize};
#[test]
@@ -175,30 +178,6 @@ fn test_to_value_struct() -> LuaResult<()> {
fn test_to_value_enum() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null())?;
#[derive(Serialize)]
struct Test {
name: String,
key: i64,
data: Option<bool>,
}
let test = Test {
name: "alex".to_string(),
key: -16,
data: None,
};
globals.set("value", lua.to_value(&test)?)?;
lua.load(
r#"
assert(value["name"] == "alex")
assert(value["key"] == -16)
assert(value["data"] == null)
"#,
)
.exec()?;
#[derive(Serialize)]
enum E {
@@ -302,6 +281,36 @@ fn test_to_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[test]
fn test_from_value_nested_tables() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
let value = lua
.load(
r#"
local table_a = {a = "a"}
local table_b = {"b"}
return {
a = table_a,
b = {table_b, table_b},
ab = {a = table_a, b = table_b}
}
"#,
)
.eval::<Value>()?;
let got = lua.from_value::<serde_json::Value>(value)?;
assert_eq!(
got,
serde_json::json!({
"a": {"a": "a"},
"b": [["b"], ["b"]],
"ab": {"a": {"a": "a"}, "b": ["b"]},
})
);
Ok(())
}
#[test]
fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
@@ -310,7 +319,7 @@ fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
struct Test {
int: u32,
seq: Vec<String>,
map: std::collections::HashMap<i32, i32>,
map: HashMap<i32, i32>,
empty: Vec<()>,
tuple: (u8, u8, u8),
}
@@ -413,3 +422,55 @@ fn test_from_value_enum_untagged() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[test]
fn test_from_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
// Deny unsupported types by default
let value = Value::Function(lua.create_function(|_, ()| Ok(()))?);
match lua.from_value::<Option<String>>(value) {
Ok(v) => panic!("expected deserialization error, got {:?}", v),
Err(Error::DeserializeError(err)) => {
assert!(err.contains("unsupported value type"))
}
Err(err) => panic!("expected `DeserializeError` error, got {:?}", err),
};
// Allow unsupported types
let value = Value::Function(lua.create_function(|_, ()| Ok(()))?);
let options = DeserializeOptions::new().deny_unsupported_types(false);
assert_eq!(lua.from_value_with::<()>(value, options)?, ());
// Allow unsupported types (in a table seq)
let value = lua.load(r#"{"a", "b", function() end, "c"}"#).eval()?;
let options = DeserializeOptions::new().deny_unsupported_types(false);
assert_eq!(
lua.from_value_with::<Vec<String>>(value, options)?,
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
// Deny recursive tables by default
let value = lua.load(r#"local t = {}; t.t = t; return t"#).eval()?;
match lua.from_value::<HashMap<String, Option<String>>>(value) {
Ok(v) => panic!("expected deserialization error, got {:?}", v),
Err(Error::DeserializeError(err)) => {
assert!(err.contains("recursive table detected"))
}
Err(err) => panic!("expected `DeserializeError` error, got {:?}", err),
};
// Serialize Lua globals table
#[derive(Debug, Deserialize)]
struct Globals {
hello: String,
}
let options = DeserializeOptions::new()
.deny_unsupported_types(false)
.deny_recursive_tables(false);
lua.load(r#"hello = "world""#).exec()?;
let globals: Globals = lua.from_value_with(Value::Table(lua.globals()), options)?;
assert_eq!(globals.hello, "world");
Ok(())
}
+28
View File
@@ -2,6 +2,7 @@ use std::collections::HashMap;
use std::iter::FromIterator;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::string::String as StdString;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::{error, f32, f64, fmt};
@@ -1086,3 +1087,30 @@ fn test_jit_version() -> Result<()> {
.contains("LuaJIT"));
Ok(())
}
#[test]
fn test_load_from_function() -> Result<()> {
let lua = Lua::new();
let i = Arc::new(AtomicU32::new(0));
let i2 = i.clone();
let func = lua.create_function(move |lua, modname: String| {
i2.fetch_add(1, Ordering::Relaxed);
let t = lua.create_table()?;
t.set("__name", modname)?;
Ok(t)
})?;
let t: Table = lua.load_from_function("my_module", func.clone())?;
assert_eq!(t.get::<_, String>("__name")?, "my_module");
assert_eq!(i.load(Ordering::Relaxed), 1);
let _: Value = lua.load_from_function("my_module", func)?;
assert_eq!(i.load(Ordering::Relaxed), 1);
let func_nil = lua.create_function(move |_, _: String| Ok(Value::Nil))?;
let v: Value = lua.load_from_function("my_module2", func_nil)?;
assert_eq!(v, Value::Boolean(true));
Ok(())
}
+102 -22
View File
@@ -36,6 +36,7 @@ fn test_user_data() -> Result<()> {
#[test]
fn test_methods() -> Result<()> {
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
struct MyUserData(i64);
impl UserData for MyUserData {
@@ -48,29 +49,38 @@ fn test_methods() -> Result<()> {
}
}
let lua = Lua::new();
let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(42))?;
globals.set("userdata", userdata.clone())?;
lua.load(
r#"
function get_it()
return userdata:get_value()
end
fn check_methods(lua: &Lua, userdata: AnyUserData) -> Result<()> {
let globals = lua.globals();
globals.set("userdata", userdata.clone())?;
lua.load(
r#"
function get_it()
return userdata:get_value()
end
function set_it(i)
return userdata:set_value(i)
end
"#,
)
.exec()?;
let get = globals.get::<_, Function>("get_it")?;
let set = globals.get::<_, Function>("set_it")?;
assert_eq!(get.call::<_, i64>(())?, 42);
userdata.borrow_mut::<MyUserData>()?.0 = 64;
assert_eq!(get.call::<_, i64>(())?, 64);
set.call::<_, ()>(100)?;
assert_eq!(get.call::<_, i64>(())?, 100);
function set_it(i)
return userdata:set_value(i)
end
"#,
)
.exec()?;
let get = globals.get::<_, Function>("get_it")?;
let set = globals.get::<_, Function>("set_it")?;
assert_eq!(get.call::<_, i64>(())?, 42);
userdata.borrow_mut::<MyUserData>()?.0 = 64;
assert_eq!(get.call::<_, i64>(())?, 64);
set.call::<_, ()>(100)?;
assert_eq!(get.call::<_, i64>(())?, 100);
Ok(())
}
let lua = Lua::new();
check_methods(&lua, lua.create_userdata(MyUserData(42))?)?;
// Additionally check serializable userdata
#[cfg(feature = "serialize")]
check_methods(&lua, lua.create_ser_userdata(MyUserData(42))?)?;
Ok(())
}
@@ -252,6 +262,76 @@ fn test_gc_userdata() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_take() -> Result<()> {
#[derive(Debug)]
struct MyUserdata(Arc<i64>);
impl UserData for MyUserdata {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("num", |_, this, ()| Ok(*this.0))
}
}
#[cfg(feature = "serialize")]
impl serde::Serialize for MyUserdata {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i64(*self.0)
}
}
fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc<i64>) -> Result<()> {
lua.globals().set("userdata", userdata.clone())?;
assert_eq!(Arc::strong_count(&rc), 2);
let userdata_copy = userdata.clone();
{
let _value = userdata.borrow::<MyUserdata>()?;
// We should not be able to take userdata if it's borrowed
match userdata_copy.take::<MyUserdata>() {
Err(Error::UserDataBorrowMutError) => {}
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
}
}
let value = userdata_copy.take::<MyUserdata>()?;
assert_eq!(*value.0, 18);
drop(value);
assert_eq!(Arc::strong_count(&rc), 1);
match userdata.borrow::<MyUserdata>() {
Err(Error::UserDataDestructed) => {}
r => panic!("expected `UserDataDestructed` error, got {:?}", r),
}
match lua.load("userdata:num()").exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
err => panic!("expected `CallbackDestructed`, got {:?}", err),
},
r => panic!("improper return for destructed userdata: {:?}", r),
}
Ok(())
}
let lua = Lua::new();
let rc = Arc::new(18);
let userdata = lua.create_userdata(MyUserdata(rc.clone()))?;
check_userdata_take(&lua, userdata, rc)?;
// Additionally check serializable userdata
#[cfg(feature = "serialize")]
{
let rc = Arc::new(18);
let userdata = lua.create_ser_userdata(MyUserdata(rc.clone()))?;
check_userdata_take(&lua, userdata, rc)?;
}
Ok(())
}
#[test]
fn test_destroy_userdata() -> Result<()> {
struct MyUserdata(Arc<()>);