mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35eedd5a5e | |||
| 53f873a482 | |||
| fc1fe2c15e | |||
| 7e7a44f4cd | |||
| 63c4861520 | |||
| 27e7facf9b | |||
| 31d32f2dda | |||
| 7d1b322e18 | |||
| d906405818 | |||
| 60fd060d47 | |||
| 9f02a9ca09 | |||
| 1d7f105585 | |||
| 1020315a9b | |||
| 628b70f561 | |||
| 8a7e546c66 | |||
| 97bd288f56 | |||
| 59253410bb | |||
| 5fb7b96704 | |||
| 17162b1e0b | |||
| 934aa3b65f | |||
| 3bffe1d7c3 | |||
| 582b2c585f | |||
| 56b6b128b5 | |||
| 1731f5d61b | |||
| 60822d12d2 | |||
| 8ff610529b | |||
| adbc9ccc9b | |||
| 84fe5f7f76 | |||
| 1fe583027b | |||
| 7b5b78fa3d | |||
| 41aae83304 | |||
| fc84e8623e | |||
| 8aae9a7c3e | |||
| 24bbd62564 | |||
| 30af919931 | |||
| 89b5981c59 |
@@ -0,0 +1 @@
|
||||
github: khvzak
|
||||
+40
-11
@@ -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")
|
||||
|
||||
+21
-2
@@ -1,3 +1,22 @@
|
||||
## 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 +26,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 +35,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
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.6.0" # remember to update html_root_url and mlua_derive
|
||||
version = "0.6.3" # 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"
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
coverage:
|
||||
status:
|
||||
patch:
|
||||
default:
|
||||
only_pulls: true
|
||||
project:
|
||||
default:
|
||||
only_pulls: true
|
||||
+12
-12
@@ -554,17 +554,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,17 +579,17 @@ 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()),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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
|
||||
|
||||
+90
@@ -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};
|
||||
@@ -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 {
|
||||
|
||||
+17
-9
@@ -72,7 +72,7 @@
|
||||
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
|
||||
|
||||
// mlua types in rustdoc of other crates get linked to here.
|
||||
#![doc(html_root_url = "https://docs.rs/mlua/0.6.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/mlua/0.6.3")]
|
||||
// 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,11 @@ mod userdata;
|
||||
mod util;
|
||||
mod value;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use crate::ffi::lua_State;
|
||||
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,7 +121,9 @@ 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")]
|
||||
@@ -139,9 +140,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,12 +166,19 @@ 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.
|
||||
///
|
||||
|
||||
+350
-253
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -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),+)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -18,4 +18,7 @@ pub use crate::AsyncThread as LuaAsyncThread;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
#[doc(inline)]
|
||||
pub use crate::{LuaSerdeExt, SerializeOptions as LuaSerializeOptions};
|
||||
pub use crate::{
|
||||
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt,
|
||||
SerializeOptions as LuaSerializeOptions,
|
||||
};
|
||||
|
||||
+10
-26
@@ -13,7 +13,7 @@ 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,
|
||||
};
|
||||
@@ -25,8 +25,8 @@ 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},
|
||||
};
|
||||
|
||||
@@ -224,7 +224,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
|
||||
@@ -460,16 +460,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 +505,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
|
||||
|
||||
+233
-44
@@ -1,3 +1,7 @@
|
||||
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};
|
||||
@@ -8,12 +12,88 @@ 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**
|
||||
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 +105,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 +123,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 +141,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 +158,13 @@ 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) = match self.value {
|
||||
Value::Table(table) => {
|
||||
let lua = table.0.lua;
|
||||
let ptr = unsafe { lua.get_ref_ptr(&table.0) };
|
||||
self.visited.borrow_mut().insert(ptr);
|
||||
|
||||
let mut iter = table.pairs::<StdString, Value>();
|
||||
let (variant, value) = match iter.next() {
|
||||
Some(v) => v?,
|
||||
None => {
|
||||
@@ -88,13 +181,22 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
|
||||
&"map with a single key",
|
||||
));
|
||||
}
|
||||
if check_value_if_skip(&value, self.options, &self.visited)? {
|
||||
return Err(de::Error::custom("bad enum value"));
|
||||
}
|
||||
|
||||
(variant, Some(value))
|
||||
}
|
||||
Value::String(variant) => (variant.to_str()?.to_owned(), 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 +204,20 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
|
||||
where
|
||||
V: de::Visitor<'de>,
|
||||
{
|
||||
match self.0 {
|
||||
match self.value {
|
||||
Value::Table(t) => {
|
||||
let lua = t.0.lua;
|
||||
let ptr = unsafe { lua.get_ref_ptr(&t.0) };
|
||||
self.visited.borrow_mut().insert(ptr);
|
||||
|
||||
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 +226,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 +259,19 @@ 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 lua = t.0.lua;
|
||||
let ptr = unsafe { lua.get_ref_ptr(&t.0) };
|
||||
self.visited.borrow_mut().insert(ptr);
|
||||
|
||||
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",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +310,11 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
|
||||
}
|
||||
}
|
||||
|
||||
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,33 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_value_if_skip(
|
||||
value: &Value,
|
||||
options: Options,
|
||||
visited: &Rc<RefCell<HashSet<*const c_void>>>,
|
||||
) -> Result<bool> {
|
||||
match value {
|
||||
Value::Table(table) => {
|
||||
let lua = table.0.lua;
|
||||
let ptr = unsafe { lua.get_ref_ptr(&table.0) };
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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`]: enum.Value.html
|
||||
///
|
||||
/// # 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,6 +231,13 @@ 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.
|
||||
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
|
||||
@@ -26,10 +26,27 @@ 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>>>,
|
||||
}
|
||||
|
||||
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>;
|
||||
|
||||
#[cfg(feature = "send")]
|
||||
|
||||
+37
-111
@@ -11,6 +11,7 @@ use std::future::Future;
|
||||
#[cfg(feature = "serialize")]
|
||||
use {
|
||||
serde::ser::{self, Serialize, Serializer},
|
||||
std::os::raw::c_void,
|
||||
std::result::Result as StdResult,
|
||||
};
|
||||
|
||||
@@ -519,8 +520,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
|
||||
///
|
||||
@@ -607,116 +610,31 @@ impl<T> UserDataCell<T> {
|
||||
}
|
||||
|
||||
// Immutably borrows the wrapped value.
|
||||
fn try_borrow(&self) -> Result<UserDataRef<T>> {
|
||||
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>> {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(T),
|
||||
#[cfg(feature = "serialize")]
|
||||
ser: *mut dyn erased_serde::Serialize,
|
||||
Serializable(*mut T, *const dyn erased_serde::Serialize),
|
||||
}
|
||||
|
||||
impl<T> UserDataWrapped<T> {
|
||||
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(data)
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
@@ -725,21 +643,15 @@ impl<T> UserDataWrapped<T> {
|
||||
T: 'static + Serialize,
|
||||
{
|
||||
let data_raw = Box::into_raw(Box::new(data));
|
||||
UserDataWrapped {
|
||||
data: data_raw,
|
||||
ser: data_raw,
|
||||
}
|
||||
UserDataWrapped::Serializable(data_raw, data_raw)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<T> Drop for UserDataWrapped<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
drop(Box::from_raw(self.data));
|
||||
#[cfg(feature = "serialize")]
|
||||
if self.data as *mut () != self.ser as *mut () {
|
||||
drop(Box::from_raw(self.ser));
|
||||
}
|
||||
if let UserDataWrapped::Serializable(data, _) = *self {
|
||||
drop(unsafe { Box::from_raw(data) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,13 +660,21 @@ impl<T> Deref for UserDataWrapped<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.data }
|
||||
match self {
|
||||
Self::Default(data) => data,
|
||||
#[cfg(feature = "serialize")]
|
||||
Self::Serializable(data, _) => unsafe { &**data },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for UserDataWrapped<T> {
|
||||
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 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,7 +726,7 @@ 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>> {
|
||||
pub fn borrow<T: 'static + UserData>(&self) -> Result<Ref<T>> {
|
||||
self.inspect(|cell| cell.try_borrow())
|
||||
}
|
||||
|
||||
@@ -816,7 +736,7 @@ 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>> {
|
||||
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<RefMut<T>> {
|
||||
self.inspect(|cell| cell.try_borrow_mut())
|
||||
}
|
||||
|
||||
@@ -1069,8 +989,14 @@ impl<'lua> Serialize for AnyUserData<'lua> {
|
||||
|
||||
lua.push_userdata_ref(&self.0, false)
|
||||
.map_err(ser::Error::custom)?;
|
||||
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
|
||||
ud.serialize(serializer)
|
||||
let ud = &*get_userdata::<UserDataCell<c_void>>(lua.state, -1);
|
||||
let data =
|
||||
ud.0.try_borrow()
|
||||
.map_err(|_| ser::Error::custom(Error::UserDataBorrowError))?;
|
||||
match *data {
|
||||
UserDataWrapped::Default(_) => UserDataSerializeError.serialize(serializer),
|
||||
UserDataWrapped::Serializable(_, ser) => (&*ser).serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
-134
@@ -167,41 +167,45 @@ 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +236,7 @@ where
|
||||
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);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -271,7 +275,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 +286,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 {
|
||||
@@ -409,7 +413,7 @@ pub unsafe fn init_userdata_metatable<T>(
|
||||
}
|
||||
}
|
||||
protect_lua(state, 3, 1, |state| {
|
||||
ffi::lua_pushcclosure(state, meta_index_impl, 3)
|
||||
ffi::lua_pushcclosure(state, meta_index_impl, 3);
|
||||
})?;
|
||||
}
|
||||
_ => mlua_panic!("improper __index type {}", index_type),
|
||||
@@ -425,7 +429,7 @@ pub unsafe fn init_userdata_metatable<T>(
|
||||
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)
|
||||
ffi::lua_pushcclosure(state, meta_newindex_impl, 2);
|
||||
})?;
|
||||
}
|
||||
_ => mlua_panic!("improper __newindex type {}", newindex_type),
|
||||
@@ -446,11 +450,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 +483,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 +494,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 +508,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 +532,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 +558,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 +574,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 +604,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,26 +638,9 @@ 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<()> {
|
||||
@@ -681,13 +670,13 @@ pub unsafe fn init_gc_metatable_for<T: Any>(
|
||||
}
|
||||
|
||||
protect_lua(state, 1, 0, |state| {
|
||||
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *mut c_void)
|
||||
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *mut 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");
|
||||
@@ -697,7 +686,6 @@ pub unsafe fn get_gc_metatable_for<T: Any>(state: *mut ffi::lua_State) {
|
||||
}
|
||||
|
||||
// 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 +695,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.splitn(2, "\nstack traceback:\n").nth(1) {
|
||||
let _ =
|
||||
write!(&mut (*err_buf), "\nstack traceback:\n{}", traceback);
|
||||
}
|
||||
}
|
||||
(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 +765,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);
|
||||
@@ -840,24 +824,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)
|
||||
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)
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]`
|
||||
|
||||
@@ -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]`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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| {
|
||||
|
||||
+57
-2
@@ -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]
|
||||
@@ -310,7 +313,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 +416,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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user