Compare commits

...

43 Commits

Author SHA1 Message Date
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
34 changed files with 1578 additions and 873 deletions
+1
View File
@@ -0,0 +1 @@
github: khvzak
+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")
+18
View File
@@ -1,3 +1,21 @@
## 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)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.6.1" # remember to update html_root_url and mlua_derive
version = "0.6.4" # 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"
+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
View File
@@ -0,0 +1,8 @@
coverage:
status:
patch:
default:
only_pulls: true
project:
default:
only_pulls: true
+25
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)?))
}
@@ -595,6 +618,7 @@ impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
}
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 +628,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),
+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
+3 -3
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")]
@@ -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()))
+90
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};
@@ -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 {
+7 -5
View File
@@ -72,7 +72,7 @@
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.6.1")]
#![doc(html_root_url = "https://docs.rs/mlua/0.6.4")]
// 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;
@@ -121,8 +120,11 @@ pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti
pub use crate::thread::AsyncThread;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(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")]
+499 -344
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)
}};
}
+4 -1
View File
@@ -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,
};
+21 -37
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},
};
@@ -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
@@ -250,7 +250,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 +264,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 +322,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 +390,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()?;
@@ -410,9 +410,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// 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 +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
+254 -47
View File
@@ -1,19 +1,99 @@
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::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**
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,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 +179,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 +202,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 +222,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 +255,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 +277,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 +304,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 +317,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 +344,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 +356,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 +381,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 +400,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 +413,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 +446,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 +461,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 +477,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 +488,57 @@ 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 ptr = unsafe { table.0.lua.get_ref_ptr(&table.0) };
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.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
}
+46 -4
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.
@@ -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,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"));
+5 -5
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.
@@ -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()
}
@@ -318,12 +318,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);
})
}
+15 -16
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))
}
}
@@ -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 {
@@ -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),
+3 -2
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::{assert_stack, check_stack, error_traceback, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored"), doc))]
@@ -135,7 +135,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));
}
@@ -341,6 +341,7 @@ where
}
#[cfg(feature = "async")]
#[inline(always)]
fn is_poll_pending(val: &MultiValue) -> bool {
match val.iter().enumerate().last() {
Some((1, Value::LightUserData(ud))) => {
+23
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,6 +63,8 @@ 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
+58 -155
View File
@@ -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,
};
@@ -20,9 +21,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, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
@@ -519,8 +518,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
///
@@ -594,11 +595,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 +610,52 @@ 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) }
}
}
/// 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> {
#[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(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(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) });
}
}
}
@@ -747,14 +663,24 @@ 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 },
}
}
}
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 },
}
}
}
@@ -806,7 +732,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,7 +743,8 @@ 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())
}
@@ -841,7 +769,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 +788,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()
};
@@ -891,7 +819,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 +846,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 +854,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),
}
}
}
@@ -1067,10 +965,15 @@ impl<'lua> Serialize for AnyUserData<'lua> {
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)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
ud.serialize(serializer)
lua.push_userdata_ref(&self.0).map_err(ser::Error::custom)?;
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),
}
}
}
}
+181 -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.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 +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);
@@ -840,24 +853,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.
+13
View File
@@ -157,18 +157,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 +181,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 +191,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;
+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| {
+87 -2
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]
@@ -302,6 +305,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 +343,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 +446,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(())
}