Compare commits

..

15 Commits

Author SHA1 Message Date
Alex Orlenko 973b5c3bf5 v0.6.0-beta.2 2021-05-13 23:10:19 +01:00
Alex Orlenko b610a79d66 Update docs & minor error handling code changes 2021-05-11 14:18:22 +01:00
Alex Orlenko fe39ae09bf Try different approach for errors handling.
Instead of convering error to CallbackError in error message handler,
do it earlier at callback_error stage.
Better fix for #44.
2021-05-11 00:53:07 +01:00
Alex Orlenko 01714d2510 Prepare 0.6.0-beta.2 release 2021-05-10 22:28:33 +01:00
Alex Orlenko 0bad4a0ff9 Fix spelling 2021-05-10 19:53:38 +01:00
Alex Orlenko 35b7504076 Improve error reporting in module mode.
Attach traceback to a WrappedError.
Fixes #44.
2021-05-10 17:45:00 +01:00
Alex Orlenko c9b8eb5418 Re-export UserDataFields/UserDataMetatable 2021-05-09 09:30:33 +01:00
Alex Orlenko ef7d123f80 Exclude mlua_derive from coverage report 2021-05-06 00:41:20 +01:00
Alex Orlenko 20cba5de5b Fix clippy warnings 2021-05-06 00:36:47 +01:00
Alex Orlenko 3e03f4201c Add chunk! macro support 2021-05-05 22:13:51 +01:00
Alex Orlenko 5199b02346 Update lua state when polling futures 2021-05-05 12:37:27 +01:00
Alex Orlenko 5293b8d6d2 Add Thread::reset() for luajit/lua54 2021-05-05 11:11:32 +01:00
Alex Orlenko 7541b6f3f3 Remove deprecated AnyUserData::has_metamethod() 2021-05-04 23:57:47 +01:00
Alex Orlenko 205510a540 Update sys align code 2021-05-04 23:55:24 +01:00
Alex Orlenko 2250421438 Fix clippy warnings 2021-05-04 23:42:33 +01:00
29 changed files with 825 additions and 157 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Generate code coverage
run: |
cargo tarpaulin --verbose --features lua53,vendored,async,send,serialize --out xml --exclude-files benches --exclude-files tests --exclude-files build --exclude-files src/ffi
cargo tarpaulin --verbose --features lua53,vendored,async,send,serialize,macros --out xml --exclude-files benches --exclude-files build --exclude-files mlua_derive --exclude-files src/ffi --exclude-files tests
- name: Upload to codecov.io
uses: codecov/codecov-action@v1
+12 -12
View File
@@ -26,8 +26,8 @@ jobs:
override: true
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --release --features "${{ matrix.lua }} vendored"
cargo build --release --features "${{ matrix.lua }} vendored async send serialize"
cargo build --release --features "${{ matrix.lua }},vendored"
cargo build --release --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' }}
@@ -51,7 +51,7 @@ jobs:
target: aarch64-apple-darwin
override: true
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }} async send serialize vendored"
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
@@ -73,7 +73,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }} async send serialize vendored"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
shell: bash
build_armv7_cross_ubuntu:
@@ -96,7 +96,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }} async send serialize vendored"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
shell: bash
test:
@@ -124,14 +124,14 @@ jobs:
override: true
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --release --features "${{ matrix.lua }} vendored"
cargo test --release --features "${{ matrix.lua }} vendored async send serialize"
cargo test --release --features "${{ matrix.lua }},vendored"
cargo test --release --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" -- --ignored
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }},vendored,async,send,serialize,macros" -- --ignored
shell: bash
test_modules:
@@ -157,8 +157,8 @@ jobs:
override: true
- name: Run ${{ matrix.lua }} module tests
run: |
(cd examples/module && cargo build --release --features "${{ matrix.lua }} vendored")
(cd tests/module && cargo test --release --features "${{ matrix.lua }} vendored")
(cd examples/module && cargo build --release --features "${{ matrix.lua }},vendored")
(cd tests/module && cargo test --release --features "${{ matrix.lua }},vendored")
shell: bash
test_modules_windows:
@@ -210,4 +210,4 @@ jobs:
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --features "${{ matrix.lua }},vendored,async,send,serialize
args: --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
+8
View File
@@ -1,3 +1,11 @@
## v0.6.0-beta.2
- [**Breaking**] Removed `AnyUserData::has_metamethod()`
- Added `Thread::reset()` for luajit/lua54 to recycle threads.
It's possible to attach a new function to a thread (coroutine).
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optinally capturing Rust variables.
- Improved error reporting (`Error`'s `__tostring` method formats full stacktraces). This is useful in the module mode.
## v0.6.0-beta.1
- New `UserDataFields` API
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.6.0-beta.1" # remember to update html_root_url and mlua_derive
version = "0.6.0-beta.2" # 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,7 +17,7 @@ with async/await features and support of writing native lua modules in Rust.
"""
[package.metadata.docs.rs]
features = ["lua53", "async", "send", "serialize"]
features = ["lua53", "async", "send", "serialize", "macros"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
@@ -38,9 +38,10 @@ module = ["mlua_derive"]
async = ["futures-core", "futures-task", "futures-util"]
send = []
serialize = ["serde", "erased-serde"]
macros = ["mlua_derive/macros"]
[dependencies]
mlua_derive = { version = "0.5", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.6.0-beta.2", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false }
once_cell = { version = "1.7" }
num-traits = { version = "0.2.14" }
+5 -5
View File
@@ -17,7 +17,7 @@ _safe_ (as far as it's possible), high level, easy to use, practical and flexibl
Started as [rlua](https://github.com/amethyst/rlua/tree/0.15.3) fork, `mlua` supports Lua 5.4, 5.3, 5.2 and 5.1 including LuaJIT (2.0.5 and 2.1 beta) and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targes are also supported).
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
[GitHub Actions]: https://github.com/khvzak/mlua/actions
@@ -25,7 +25,7 @@ Started as [rlua](https://github.com/amethyst/rlua/tree/0.15.3) fork, `mlua` sup
### Feature flags
`mlua` uses feature flags to reduce the amount of depenendies, compiled code and allow to choose only required set of features.
`mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only required set of features.
Below is a list of the available feature flags. By default `mlua` does not enable any features.
* `lua54`: activate Lua [5.4] support
@@ -73,7 +73,7 @@ With `serialize` feature flag enabled, `mlua` allows you to serialize/deserializ
### Compiling
You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit`, according to the choosen Lua version.
You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit`, according to the chosen Lua version.
By default `mlua` uses `pkg-config` tool to find lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes could be more preferable to use a custom lua library.
@@ -85,7 +85,7 @@ An example how to use them:
my_project $ LUA_INC=$HOME/tmp/lua-5.2.4/src LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build
```
`mlua` also supports vendored lua/luajit using the auxilary crates [lua-src](https://crates.io/crates/lua-src) and
`mlua` also supports vendored lua/luajit using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
[luajit-src](https://crates.io/crates/luajit-src).
Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with `mlua`.
@@ -233,7 +233,7 @@ If you encounter them, a bug report would be very welcome:
+ If your program panics with a message that contains the string "mlua internal error", this is a bug.
+ Lua C API errors are handled by lonjmp. All instances where the Lua C API would otherwise longjmp over calling stack frames should be guarded against, except in internal callbacks where this is intentional. If you detect that `mlua` is triggering a longjmp over your Rust stack frames, this is a bug!
+ Lua C API errors are handled by longjmp. All instances where the Lua C API would otherwise longjmp over calling stack frames should be guarded against, except in internal callbacks where this is intentional. If you detect that `mlua` is triggering a longjmp over your Rust stack frames, this is a bug!
+ If you detect that, after catching a panic or during a Drop triggered from a panic, a `Lua` or handle method is triggering other bugs or there is a Lua stack space leak, this is a bug. `mlua` instances are supposed to remain fully usable in the face of user generated panics. This guarantee does not extend to panics marked with "mlua internal error" simply because that is already indicative of a separate bug.
+3 -3
View File
@@ -22,12 +22,12 @@ pub fn probe_lua() -> PathBuf {
let need_lua_lib = cfg!(any(not(feature = "module"), target_os = "windows"));
if include_dir != "" {
if !include_dir.is_empty() {
if need_lua_lib {
if lib_dir == "" {
if lib_dir.is_empty() {
panic!("LUA_LIB is not set");
}
if lua_lib == "" {
if lua_lib.is_empty() {
panic!("LUA_LIB_NAME is not set");
}
+9 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.5.0"
version = "0.6.0-beta.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
description = "Procedural macros for the mlua crate."
@@ -11,7 +11,14 @@ license = "MIT"
[lib]
proc-macro = true
[features]
macros = ["proc-macro-error", "itertools", "regex", "once_cell"]
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error = { version = "1.0", optional = true }
syn = { version = "1.0", features = ["full"] }
itertools = { version = "0.10", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.5", optional = true }
+105
View File
@@ -0,0 +1,105 @@
use proc_macro::{TokenStream, TokenTree};
use crate::token::{Pos, Token, Tokens};
#[derive(Debug, Clone)]
pub(crate) struct Capture {
key: Token,
rust: TokenTree,
}
impl Capture {
fn new(key: Token, rust: TokenTree) -> Self {
Self { key, rust }
}
/// Token string inside `chunk!`
pub(crate) fn key(&self) -> &Token {
&self.key
}
/// As rust variable, e.g. `x`
pub(crate) fn as_rust(&self) -> &TokenTree {
&self.rust
}
}
#[derive(Debug)]
pub(crate) struct Captures(Vec<Capture>);
impl Captures {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn add(&mut self, token: &Token) -> Capture {
let tt = token.tree();
let key = token.clone();
match self.0.iter().find(|arg| arg.key() == &key) {
Some(arg) => arg.clone(),
None => {
let arg = Capture::new(key, tt.clone());
self.0.push(arg.clone());
arg
}
}
}
pub(crate) fn captures(&self) -> &[Capture] {
&self.0
}
}
#[derive(Debug)]
pub(crate) struct Chunk {
source: String,
caps: Captures,
}
impl Chunk {
pub(crate) fn new(tokens: TokenStream) -> Self {
let tokens = Tokens::retokenize(tokens);
let mut source = String::new();
let mut caps = Captures::new();
let mut pos: Option<Pos> = None;
for t in tokens {
if t.is_cap() {
caps.add(&t);
}
let (line, col) = (t.start().line, t.start().column);
let (prev_line, prev_col) = pos
.take()
.map(|lc| (lc.line, lc.column))
.unwrap_or_else(|| (line, col));
#[allow(clippy::comparison_chain)]
if line > prev_line {
source.push('\n');
} else if line == prev_line {
for _ in 0..col.saturating_sub(prev_col) {
source.push(' ');
}
}
source.push_str(&t.to_string());
pos = Some(t.end());
}
Self {
source: source.trim_end().to_string(),
caps,
}
}
pub(crate) fn source(&self) -> &str {
&self.source
}
pub(crate) fn captures(&self) -> &[Capture] {
self.caps.captures()
}
}
+86 -3
View File
@@ -1,16 +1,20 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote_spanned;
use syn::{parse_macro_input, spanned::Spanned, AttributeArgs, Error, ItemFn};
#[cfg(feature = "macros")]
use {
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error::proc_macro_error, quote::quote,
};
#[proc_macro_attribute]
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as AttributeArgs);
let item = parse_macro_input!(item as ItemFn);
if args.len() > 0 {
if !args.is_empty() {
let err = Error::new(Span::call_site(), "the number of arguments must be zero")
.to_compile_error();
return err.into();
@@ -35,3 +39,82 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
wrapped.into()
}
#[cfg(feature = "macros")]
fn to_ident(tt: &TokenTree) -> TokenStream2 {
let s: TokenStream = tt.clone().into();
s.into()
}
#[cfg(feature = "macros")]
#[proc_macro]
#[proc_macro_error]
pub fn chunk(input: TokenStream) -> TokenStream {
let chunk = Chunk::new(input);
let source = chunk.source();
let caps_len = chunk.captures().len();
let caps = chunk.captures().iter().map(|cap| {
let cap_name = cap.as_rust().to_string();
let cap = to_ident(cap.as_rust());
quote! { env.raw_set(#cap_name, #cap)?; }
});
let wrapped_code = quote! {{
use ::mlua::{AsChunk, ChunkMode, Lua, Result, Value};
use ::std::marker::PhantomData;
use ::std::sync::Mutex;
fn annotate<'a, F: FnOnce(&'a Lua) -> Result<Value<'a>>>(f: F) -> F { f }
struct InnerChunk<'a, F: FnOnce(&'a Lua) -> Result<Value<'a>>>(Mutex<Option<F>>, PhantomData<&'a ()>);
impl<'lua, F> AsChunk<'lua> for InnerChunk<'lua, F>
where
F: FnOnce(&'lua Lua) -> Result<Value<'lua>>,
{
fn source(&self) -> &[u8] {
(#source).as_bytes()
}
fn env(&self, lua: &'lua Lua) -> Option<Result<Value<'lua>>> {
if #caps_len > 0 {
if let Ok(mut make_env) = self.0.lock() {
if let Some(make_env) = make_env.take() {
return Some(make_env(lua));
}
}
}
None
}
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
}
let make_env = annotate(move |lua: &Lua| -> Result<Value> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", globals.clone())?;
meta.raw_set("__newindex", globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta));
Ok(Value::Table(env))
});
&InnerChunk(Mutex::new(Some(make_env)), PhantomData)
}};
wrapped_code.into()
}
#[cfg(feature = "macros")]
mod chunk;
#[cfg(feature = "macros")]
mod token;
+234
View File
@@ -0,0 +1,234 @@
use std::{
cmp::{Eq, PartialEq},
fmt::{self, Display, Formatter},
iter::IntoIterator,
vec::IntoIter,
};
use itertools::Itertools;
use once_cell::sync::Lazy;
use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
use proc_macro2::Span as Span2;
use regex::Regex;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Pos {
pub(crate) line: usize,
pub(crate) column: usize,
}
impl Pos {
fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
fn left(&self) -> Self {
Self {
line: self.line,
column: self.column.saturating_sub(1),
}
}
fn right(&self) -> Self {
Self {
line: self.line,
column: self.column.saturating_add(1),
}
}
}
fn span_pos(span: &Span) -> (Pos, Pos) {
let span2: Span2 = (*span).into();
let start = span2.start();
let end = span2.end();
// In stable, line/column information is not provided
// and set to 0 (line is 1-indexed)
if start.line == 0 || end.line == 0 {
return fallback_span_pos(span);
}
(
Pos::new(start.line, start.column),
Pos::new(end.line, end.column),
)
}
fn parse_pos(span: &Span) -> Option<(usize, usize)> {
// Workaround to somehow retrieve location information in span in stable rust :(
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap());
match RE.captures(&format!("{:?}", span)) {
Some(caps) => match (caps.get(1), caps.get(2)) {
(Some(start), Some(end)) => Some((
match start.as_str().parse() {
Ok(v) => v,
_ => return None,
},
match end.as_str().parse() {
Ok(v) => v,
_ => return None,
},
)),
_ => None,
},
None => None,
}
}
fn fallback_span_pos(span: &Span) -> (Pos, Pos) {
let (start, end) = match parse_pos(span) {
Some(v) => v,
None => proc_macro_error::abort_call_site!(
"Cannot retrieve span information; please use nightly"
),
};
(Pos::new(1, start), Pos::new(1, end))
}
/// Attribute of token.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TokenAttr {
/// No attribute
None,
/// Starts with `$`
Cap,
}
#[derive(Clone, Debug)]
pub(crate) struct Token {
source: String,
tree: TokenTree,
start: Pos,
end: Pos,
attr: TokenAttr,
}
impl PartialEq for Token {
fn eq(&self, other: &Self) -> bool {
self.source == other.source && self.attr == other.attr
}
}
impl Eq for Token {}
impl Token {
fn new(tree: TokenTree) -> Self {
let (start, end) = span_pos(&tree.span());
Self {
source: tree.to_string(),
start,
end,
tree,
attr: TokenAttr::None,
}
}
fn new_delim(source: String, tree: TokenTree, open: bool) -> Self {
let (start, end) = span_pos(&tree.span());
let (start, end) = if open {
(start, start.right())
} else {
(end.left(), end)
};
Self {
source,
tree,
start,
end,
attr: TokenAttr::None,
}
}
pub(crate) fn tree(&self) -> &TokenTree {
&self.tree
}
pub(crate) fn is_cap(&self) -> bool {
self.attr == TokenAttr::Cap
}
pub(crate) fn start(&self) -> Pos {
self.start
}
pub(crate) fn end(&self) -> Pos {
self.end
}
fn is(&self, s: &str) -> bool {
self.source == s
}
fn attr(mut self, attr: TokenAttr) -> Self {
self.attr = attr;
self
}
}
#[derive(Debug)]
pub(crate) struct Tokens(pub(crate) Vec<Token>);
impl Tokens {
pub(crate) fn retokenize(tt: TokenStream) -> Tokens {
Tokens(
tt.into_iter()
.map(Tokens::from)
.flatten()
.peekable()
.batching(|iter| {
// Find variable tokens
let t = iter.next()?;
if t.is("$") {
// `$` + `ident` => `$ident`
let t = iter.next().expect("$ must trail an identifier");
Some(t.attr(TokenAttr::Cap))
} else {
Some(t)
}
})
.collect(),
)
}
}
impl IntoIterator for Tokens {
type Item = Token;
type IntoIter = IntoIter<Token>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl From<TokenTree> for Tokens {
fn from(tt: TokenTree) -> Self {
let tts = match tt.clone() {
TokenTree::Group(g) => {
let (b, e) = match g.delimiter() {
Delimiter::Parenthesis => ("(", ")"),
Delimiter::Brace => ("{", "}"),
Delimiter::Bracket => ("[", "]"),
Delimiter::None => ("", ""),
};
let (b, e) = (b.into(), e.into());
vec![Token::new_delim(b, tt.clone(), true)]
.into_iter()
.chain(g.stream().into_iter().map(Tokens::from).flatten())
.chain(vec![Token::new_delim(e, tt, false)])
.collect()
}
_ => vec![Token::new(tt)],
};
Tokens(tts)
}
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.source)
}
}
+2
View File
@@ -1,3 +1,5 @@
#![allow(clippy::wrong_self_convention)]
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::{CStr, CString};
+5 -3
View File
@@ -1,3 +1,5 @@
#![allow(clippy::wrong_self_convention)]
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
@@ -88,7 +90,7 @@ pub enum Error {
},
/// [`Thread::resume`] was called on an inactive coroutine.
///
/// A coroutine is inactive if its main function has returned or if an error has occured inside
/// A coroutine is inactive if its main function has returned or if an error has occurred inside
/// the coroutine.
///
/// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this
@@ -152,7 +154,7 @@ pub enum Error {
/// Original error returned by the Rust code.
cause: Arc<Error>,
},
/// A Rust panic that was previosly resumed, returned again.
/// A Rust panic that was previously resumed, returned again.
///
/// This error can occur only when a Rust panic resumed previously was recovered
/// and returned again.
@@ -246,7 +248,7 @@ impl fmt::Display for Error {
write!(fmt, "RegistryKey used from different Lua state")
}
Error::CallbackError { ref traceback, .. } => {
write!(fmt, "callback error: {}", traceback)
write!(fmt, "callback error\n{}", traceback)
}
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
+2 -1
View File
@@ -538,6 +538,7 @@ pub unsafe fn lua_stringtonumber(L: *mut lua_State, s: *const c_char) -> usize {
0
}
#[allow(clippy::branches_sharing_code)]
pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void {
use super::glue::LUA_EXTRASPACE;
@@ -679,7 +680,7 @@ pub unsafe fn luaL_traceback(
msg: *const c_char,
mut level: c_int,
) {
let mut ar: lua_Debug = std::mem::zeroed();
let mut ar: lua_Debug = mem::zeroed();
let top = lua_gettop(L);
let numlevels = compat53_countlevels(L1);
let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 {
+17 -1
View File
@@ -156,8 +156,13 @@ extern "C" {
pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State;
pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
#[cfg(feature = "lua54")]
pub fn lua_resetthread(L: *mut lua_State) -> c_int;
#[link_name = "lua_resetthread"]
pub fn lua_resetthread_54(L: *mut lua_State) -> c_int;
#[cfg(all(feature = "luajit", feature = "vendored"))]
#[link_name = "lua_resetthread"]
pub fn lua_resetthread_jit(L: *mut lua_State, th: *mut lua_State);
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
@@ -216,6 +221,17 @@ extern "C" {
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
pub unsafe fn lua_resetthread(_L: *mut lua_State, th: *mut lua_State) -> c_int {
#[cfg(all(feature = "luajit", feature = "vendored"))]
{
lua_resetthread_jit(_L, th);
LUA_OK
}
#[cfg(feature = "lua54")]
lua_resetthread_54(th)
}
// Comparison and arithmetic functions
pub const LUA_OPADD: c_int = 0;
pub const LUA_OPSUB: c_int = 1;
+9 -4
View File
@@ -160,8 +160,8 @@ pub use self::lua::{
#[cfg(feature = "lua54")]
pub use self::lua::{
lua_getiuservalue, lua_newuserdatauv, lua_resetthread, lua_setcstacklimit, lua_setiuservalue,
lua_setwarnf, lua_toclose, lua_warning,
lua_getiuservalue, lua_newuserdatauv, lua_setcstacklimit, lua_setiuservalue, lua_setwarnf,
lua_toclose, lua_warning,
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
@@ -170,6 +170,9 @@ pub use self::lua::{lua_isyieldable, lua_version};
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub use self::lua::{lua_callk, lua_pcallk, lua_upvalueid, lua_upvaluejoin, lua_yieldk};
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
pub use self::lua::lua_resetthread;
// auxiliary library types
pub use self::lauxlib::luaL_Reg;
@@ -253,16 +256,18 @@ pub use self::lualib::{LUA_FFILIBNAME, LUA_JITLIBNAME};
// Not actually defined in lua.h / luaconf.h
pub const LUA_MAX_UPVALUES: c_int = 255;
// Copied from https://github.com/rust-lang/rust/blob/master/src/libstd/sys_common/alloc.rs
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/common/alloc.rs
#[cfg(all(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "mips",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "asmjs",
target_arch = "wasm32",
target_arch = "hexagon"
target_arch = "hexagon",
target_arch = "riscv32"
)))]
pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(all(any(
+37 -31
View File
@@ -31,21 +31,37 @@ size_t MLUA_WRAPPED_PANIC_SIZE = 0;
const void *MLUA_WRAPPED_ERROR_KEY = NULL;
const void *MLUA_WRAPPED_PANIC_KEY = NULL;
extern void wrapped_error_traceback(lua_State *L, int error_idx, void *error_ud,
int has_traceback);
extern void wrapped_error_traceback(lua_State *L, int error_idx,
int traceback_idx);
extern int mlua_hook_proc(lua_State *L, lua_Debug *ar);
#define max(a, b) (a > b ? a : b)
// I believe luaL_traceback < 5.4 requires this much free stack to not error.
// 5.4 uses luaL_Buffer
const int LUA_TRACEBACK_STACK = 11;
typedef struct {
const char *data;
size_t len;
} StringArg;
static void handle_wrapped_error(lua_State *L) {
if (lua_checkstack(L, LUA_TRACEBACK_STACK) != 0) {
luaL_traceback(L, L, NULL, 0);
// Convert to CallbackError and attach traceback
wrapped_error_traceback(L, -2, -1);
lua_pop(L, 1);
} else {
// Convert to CallbackError with error message as a traceback
wrapped_error_traceback(L, -1, 0);
}
}
// A wrapper around Rust function to protect from triggering longjmp in Rust.
// Rust callback expected to return -1 in case of errors or number of output
// values.
// Rust callback expected to return positive number of output values or
// -1 in case of error, -2 in case of panic.
static int lua_call_rust(lua_State *L) {
int nargs = lua_gettop(L);
@@ -67,7 +83,10 @@ static int lua_call_rust(lua_State *L) {
lua_CFunction rust_callback = lua_touserdata(L, lua_upvalueindex(1));
int ret = rust_callback(L);
if (ret == -1) {
if (ret < 0) {
if (ret == -1 /* WrappedError */) {
handle_wrapped_error(L);
}
lua_error(L);
}
@@ -79,7 +98,10 @@ void lua_call_mlua_hook_proc(lua_State *L, lua_Debug *ar) {
lua_newuserdata(L, max(MLUA_WRAPPED_ERROR_SIZE, MLUA_WRAPPED_PANIC_SIZE));
lua_rotate(L, 1, 1);
int ret = mlua_hook_proc(L, ar);
if (ret == -1) {
if (ret < 0) {
if (ret == -1 /* WrappedError */) {
handle_wrapped_error(L);
}
lua_error(L);
}
}
@@ -393,42 +415,26 @@ int is_wrapped_struct(lua_State *state, int index, const void *key) {
return res;
}
// Takes an error at the top of the stack, and if it is a WrappedError, converts
// it to an Error::CallbackError with a traceback, if it is some lua type,
// prints the error along with a traceback, and if it is a WrappedPanic, does
// Takes an error at the top of the stack and converts Lua errors into a string
// with attached traceback. If the error is a WrappedError or WrappedPanic, does
// not modify it. This function does its best to avoid triggering another error
// and shadowing previous rust errors, but it may trigger Lua errors that shadow
// rust errors under certain memory conditions. This function ensures that such
// behavior will *never* occur with a rust panic, however.
// and shadowing previous rust errors.
int error_traceback(lua_State *state) {
// I believe luaL_traceback < 5.4 requires this much free stack to not error.
// 5.4 uses luaL_Buffer
const int LUA_TRACEBACK_STACK = 11;
if (lua_checkstack(state, 2) == 0) {
// If we don't have enough stack space to even check the error type, do
// nothing so we don't risk shadowing a rust panic.
return 1;
}
if (is_wrapped_struct(state, -1, MLUA_WRAPPED_ERROR_KEY) != 0) {
int error_idx = lua_absindex(state, -1);
// lua_newuserdata and luaL_traceback may error
void *error_ud = lua_newuserdata(state, MLUA_WRAPPED_ERROR_SIZE);
int has_traceback = 0;
if (lua_checkstack(state, LUA_TRACEBACK_STACK) != 0) {
luaL_traceback(state, state, NULL, 0);
has_traceback = 1;
}
wrapped_error_traceback(state, error_idx, error_ud, has_traceback);
if (MLUA_WRAPPED_PANIC_KEY == NULL ||
is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY) ||
is_wrapped_struct(state, -1, MLUA_WRAPPED_ERROR_KEY)) {
return 1;
}
if (MLUA_WRAPPED_PANIC_KEY != NULL &&
!is_wrapped_struct(state, -1, MLUA_WRAPPED_PANIC_KEY) &&
lua_checkstack(state, LUA_TRACEBACK_STACK) != 0) {
const char *s = luaL_tolstring(state, -1, NULL);
luaL_traceback(state, state, s, 0);
const char *s = luaL_tolstring(state, -1, NULL);
if (lua_checkstack(state, LUA_TRACEBACK_STACK) != 0) {
luaL_traceback(state, state, s, 1);
lua_remove(state, -2);
}
+1 -1
View File
@@ -90,7 +90,7 @@ impl<'lua> Function<'lua> {
/// Returns a Feature that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internaly it wraps the function to an [`AsyncThread`].
/// Internally it wraps the function to an [`AsyncThread`].
///
/// Requires `feature = "async"`
///
+1 -1
View File
@@ -11,7 +11,7 @@ use crate::util::callback_error;
/// The `Debug` structure is provided as a parameter to the hook function set with
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the
/// Lua code executing at the time that the hook function was called. Further information can be
/// found in the [Lua 5.3 documentaton][lua_doc].
/// found in the [Lua 5.3 documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.3/manual.html#lua_Debug
/// [`Lua::set_hook`]: struct.Lua.html#method.set_hook
+60 -8
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.0-beta.1")]
#![doc(html_root_url = "https://docs.rs/mlua/0.6.0-beta.2")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
@@ -98,12 +98,13 @@ mod userdata;
mod util;
mod value;
#[doc(hidden)]
pub use crate::ffi::lua_State;
pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers};
pub use crate::lua::{Chunk, ChunkMode, GCMode, Lua, LuaOptions};
pub use crate::lua::{AsChunk, Chunk, ChunkMode, GCMode, Lua, LuaOptions};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
pub use crate::stdlib::StdLib;
@@ -111,7 +112,9 @@ pub use crate::string::String;
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods,
};
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
@@ -126,11 +129,60 @@ pub mod prelude;
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
// Re-export #[mlua_derive::lua_module].
#[cfg(feature = "mlua_derive")]
#[cfg(any(feature = "mlua_derive"))]
#[allow(unused_imports)]
#[macro_use]
extern crate mlua_derive;
#[cfg(feature = "mlua_derive")]
#[doc(hidden)]
pub use mlua_derive::*;
/// Create a type that implements [`AsChunk`] and can capture Rust variables.
///
/// This macro allows to write Lua code directly in Rust code.
///
/// Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
/// User Rust types needs to implement [`UserData`] or [`ToLua`] traits.
///
/// Captured variables are moved into the chunk.
///
/// ```
/// use mlua::{Lua, Result, chunk};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let name = "Rustacean";
/// lua.load(chunk! {
/// print("hello, " .. $name)
/// }).exec()
/// }
/// ```
///
/// ## Syntax issues
///
/// Since the Rust tokenizer will tokenize Lua code, this imposes some restrictions.
/// The main thing to remember is:
///
/// - Use double quoted strings (`""`) instead of single quoted strings (`''`).
///
/// (Single quoted strings only work if they contain a single character, since in Rust,
/// `'a'` is a character literal).
///
/// Other minor limitations:
///
/// - Certain escape codes in string literals.
/// (Specifically: `\a`, `\b`, `\f`, `\v`, `\123` (octal escape codes), `\u`, and `\U`).
///
/// These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`
///
/// - The `//` (floor division) operator is unusable, as its start a comment.
///
/// Everything else should work.
///
/// [`AsChunk`]: trait.AsChunk.html
/// [`UserData`]: trait.UserData.html
/// [`ToLua`]: trait.ToLua.html
#[cfg(any(feature = "macros"))]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
#[cfg(any(feature = "module"))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
+63 -19
View File
@@ -97,7 +97,7 @@ pub enum GCMode {
Generational,
}
/// Controls Lua interpreter behaviour such as Rust panics handling.
/// Controls Lua interpreter behavior such as Rust panics handling.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct LuaOptions {
@@ -107,7 +107,7 @@ pub struct LuaOptions {
/// Also in Lua 5.1 adds ability to provide arguments to [`xpcall`] similar to Lua >= 5.2.
///
/// If enabled, keeps [`pcall`]/[`xpcall`] unmodified.
/// Panics are still automatically resumed if returned back to the Rust side.
/// Panics are still automatically resumed if returned to the Rust side.
///
/// Default: **true**
///
@@ -125,7 +125,7 @@ impl Default for LuaOptions {
}
impl LuaOptions {
/// Retruns a new instance of `LuaOptions` with default parameters.
/// Returns a new instance of `LuaOptions` with default parameters.
pub fn new() -> Self {
Self::default()
}
@@ -206,7 +206,7 @@ impl Lua {
/// Creates a new Lua state and loads the specified safe subset of the standard libraries.
///
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
/// Use the [`StdLib`] flags to specify the libraries you want to load.
///
/// # Safety
/// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe
@@ -243,7 +243,7 @@ impl Lua {
/// Creates a new Lua state and loads the specified subset of the standard libraries.
///
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
/// Use the [`StdLib`] flags to specify the libraries you want to load.
///
/// # Safety
/// The created Lua state will not have safety guarantees and allow to load C modules.
@@ -300,7 +300,7 @@ impl Lua {
if !new_ptr.is_null() {
mem_info.used_memory += mem_diff;
} else if !ptr.is_null() && nsize < osize {
// Should not happend
// Should not happen
alloc::handle_alloc_error(new_layout);
}
@@ -459,7 +459,7 @@ impl Lua {
/// Loads the specified subset of the standard libraries into an existing Lua state.
///
/// Use the [`StdLib`] flags to specifiy the libraries you want to load.
/// Use the [`StdLib`] flags to specify the libraries you want to load.
///
/// [`StdLib`]: struct.StdLib.html
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
@@ -769,14 +769,14 @@ impl Lua {
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
pub fn load<'lua, 'a, S>(&'lua self, source: &'a S) -> Chunk<'lua, 'a>
where
S: AsRef<[u8]> + ?Sized,
S: AsChunk<'lua> + ?Sized,
{
Chunk {
lua: self,
source: source.as_ref(),
name: None,
env: None,
mode: None,
source: source.source(),
name: source.name(),
env: source.env(self),
mode: source.mode(),
}
}
@@ -1840,6 +1840,7 @@ impl Lua {
}
let lua = &mut *lua;
lua.state = state;
// Try to get an outer poll waker
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
@@ -2009,7 +2010,7 @@ pub struct Chunk<'lua, 'a> {
lua: &'lua Lua,
source: &'a [u8],
name: Option<CString>,
env: Option<Value<'lua>>,
env: Option<Result<Value<'lua>>>,
mode: Option<ChunkMode>,
}
@@ -2020,6 +2021,32 @@ pub enum ChunkMode {
Binary,
}
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.3/manual.html#3.3.2
/// [`Chunk`]: struct.Chunk.html
pub trait AsChunk<'lua> {
/// Returns chunk data (can be text or binary)
fn source(&self) -> &[u8];
/// Returns optional chunk name
fn name(&self) -> Option<CString> {
None
}
/// Returns optional chunk [environment]
///
/// [environment]: https://www.lua.org/manual/5.3/manual.html#2.2
fn env(&self, _lua: &'lua Lua) -> Option<Result<Value<'lua>>> {
None
}
/// Returns optional chunk mode (text or binary)
fn mode(&self) -> Option<ChunkMode> {
None
}
}
impl<'lua, 'a> Chunk<'lua, 'a> {
/// Sets the name of this chunk, which results in more informative error traces.
pub fn set_name<S: AsRef<[u8]> + ?Sized>(mut self, name: &S) -> Result<Chunk<'lua, 'a>> {
@@ -2045,7 +2072,8 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// necessary to populate the environment in order for scripts using custom environments to be
/// useful.
pub fn set_environment<V: ToLua<'lua>>(mut self, env: V) -> Result<Chunk<'lua, 'a>> {
self.env = Some(env.to_lua(self.lua)?);
// Prefer to propagate errors here and wrap to `Ok`
self.env = Some(Ok(env.to_lua(self.lua)?));
Ok(self)
}
@@ -2099,7 +2127,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
self.env()?,
self.mode,
) {
function.call(())
@@ -2127,7 +2155,10 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
match self.env() {
Ok(env) => env,
Err(e) => return Box::pin(future::err(e)),
},
self.mode,
) {
function.call_async(())
@@ -2136,14 +2167,14 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
}
}
/// Load the chunk function and call it with the given arguemnts.
/// Load the chunk function and call it with the given arguments.
///
/// This is equivalent to `into_function` and calling the resulting function.
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
self.into_function()?.call(args)
}
/// Load the chunk function and asynchronously call it with the given arguemnts.
/// Load the chunk function and asynchronously call it with the given arguments.
///
/// See [`Chunk::call`] for more details.
///
@@ -2169,7 +2200,14 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// This simply compiles the chunk without actually executing it.
pub fn into_function(self) -> Result<Function<'lua>> {
self.lua
.load_chunk(self.source, self.name.as_ref(), self.env, self.mode)
.load_chunk(self.source, self.name.as_ref(), self.env()?, self.mode)
}
fn env(&self) -> Result<Option<Value<'lua>>> {
match self.env {
None => Ok(None),
Some(ref env) => env.clone().map(Some),
}
}
fn expression_source(&self) -> Vec<u8> {
@@ -2180,6 +2218,12 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
}
}
impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T {
fn source(&self) -> &[u8] {
self.as_ref()
}
}
unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> {
#[cfg(feature = "luajit")]
// Stop collector during library initialization
+2
View File
@@ -1,3 +1,5 @@
#![allow(clippy::wrong_self_convention)]
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
use std::result::Result as StdResult;
+1
View File
@@ -9,6 +9,7 @@ pub use crate::{
Result as LuaResult, String as LuaString, Table as LuaTable, TableExt as LuaTableExt,
TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread,
ThreadStatus as LuaThreadStatus, ToLua, ToLuaMulti, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, Value as LuaValue,
};
+1 -1
View File
@@ -19,7 +19,7 @@ pub struct Serializer<'lua> {
options: Options,
}
/// A struct with options to change default serializer behaviour.
/// A struct with options to change default serializer behavior.
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Options {
+3 -3
View File
@@ -243,10 +243,10 @@ impl<'lua> Table<'lua> {
/// Removes a key from the table.
///
/// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
/// and erases element `table[key]`. The complexity is O(n) in worst case,
/// and erases element `table[key]`. The complexity is O(n) in the worst case,
/// where n is the table length.
///
/// For othey key types this is equivalent to setting `table[key] = nil`.
/// For other key types this is equivalent to setting `table[key] = nil`.
pub fn raw_remove<K: ToLua<'lua>>(&self, key: K) -> Result<()> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
@@ -378,7 +378,7 @@ impl<'lua> Table<'lua> {
/// Consume this table and return an iterator over all values in the sequence part of the table.
///
/// The iterator will yield all values `t[1]`, `t[2]`, and so on, until a `nil` value is
/// encountered. This mirrors the behaviour of Lua's `ipairs` function and will invoke the
/// encountered. This mirrors the behavior of Lua's `ipairs` function and will invoke the
/// `__index` metamethod according to the usual rules. However, the deprecated `__ipairs`
/// metatable will not be called.
///
+40
View File
@@ -7,6 +7,9 @@ use crate::types::LuaRef;
use crate::util::{assert_stack, check_stack, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored"), doc))]
use crate::function::Function;
#[cfg(feature = "async")]
use {
crate::{
@@ -170,6 +173,43 @@ impl<'lua> Thread<'lua> {
}
}
/// Resets a thread
///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
/// Returns a error in case of either the original error that stopped the thread or errors
/// in closing methods.
///
/// In [LuaJIT]: resets to the initial state of a newly created Lua thread.
/// Lua threads in arbitrary states (like yielded or errored) can be reset properly.
///
/// Sets a Lua function for the thread afterwards.
///
/// Requires `feature = "lua54"` OR `feature = "luajit,vendored"`
///
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread
/// [LuaJIT]: https://github.com/openresty/luajit2#lua_resetthread
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored"), doc))]
pub fn reset(&self, func: Function<'lua>) -> Result<()> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 2)?;
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1);
let ret = ffi::lua_resetthread(lua.state, thread_state);
if ret != ffi::LUA_OK {
return Err(pop_error(thread_state, ret));
}
lua.push_ref(&func.0);
ffi::lua_xmove(lua.state, thread_state, 1);
Ok(())
}
}
/// Converts Thread to an AsyncThread which implements Future and Stream traits.
///
/// `args` are passed as arguments to the thread function for first call.
+5 -27
View File
@@ -21,7 +21,10 @@ use crate::lua::Lua;
use crate::table::{Table, TablePairs};
use crate::types::{LuaRef, MaybeSend};
use crate::util::{check_stack, get_destructed_userdata_metatable, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti, Value};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
use crate::value::Value;
/// Kinds of metamethods that can be overridden.
///
@@ -758,7 +761,7 @@ impl<'lua> AnyUserData<'lua> {
/// Returns a metatable of this `UserData`.
///
/// Returned [`UserDataMetatable`] object wraps the original metatable and
/// provides safe access to it methods.
/// provides safe access to its methods.
///
/// For `T: UserData + 'static` returned metatable is shared among all instances of type `T`.
///
@@ -767,31 +770,6 @@ impl<'lua> AnyUserData<'lua> {
self.get_raw_metatable().map(UserDataMetatable)
}
/// Checks for a metamethod in this `AnyUserData`.
///
/// This function is deprecated and will be removed in v0.7.
/// Please use [`get_metatable`] function instead.
///
/// [`get_metatable`]: #method.get_metatable
#[deprecated(
since = "0.6.0",
note = "Please use the get_metatable function instead"
)]
pub fn has_metamethod(&self, method: MetaMethod) -> Result<bool> {
match self.get_raw_metatable() {
Ok(mt) => {
let name = self.0.lua.create_string(method.validate()?.name())?;
if let Value::Nil = mt.raw_get(name)? {
Ok(false)
} else {
Ok(true)
}
}
Err(Error::UserDataTypeMismatch) => Ok(false),
Err(e) => Err(e),
}
}
fn get_raw_metatable(&self) -> Result<Table<'lua>> {
unsafe {
let lua = self.0.lua;
+37 -28
View File
@@ -1,5 +1,6 @@
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt::Write;
use std::os::raw::{c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
@@ -44,7 +45,7 @@ pub struct StackGuard {
}
impl StackGuard {
// Creates a StackGuard instance with wa record of the stack size, and on Drop will check the
// 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.
pub unsafe fn new(state: *mut ffi::lua_State) -> StackGuard {
@@ -323,45 +324,30 @@ where
ptr::write(error_ud as *mut WrappedPanic, WrappedPanic(Some(p)));
get_gc_metatable_for::<WrappedPanic>(state);
ffi::lua_setmetatable(state, -2);
-1
-2
}
}
}
// A part of the C shim (error_traceback).
// Receives absolute index of error in the stack, a pointer to pre-allocated WrappedError memory,
// and optional boolean flag if a traceback value is on top of the stack.
// A part of the C shim for errors handling.
// Receives indexes of error and traceback (optional) in the stack.
// Converts error into a `CallbackError` and attaches the traceback provided.
#[no_mangle]
pub unsafe extern "C" fn wrapped_error_traceback(
state: *mut ffi::lua_State,
error_idx: c_int,
error_ud: *mut c_void,
has_traceback: c_int,
traceback_idx: c_int,
) {
let error = mlua_expect!(
get_wrapped_error(state, error_idx).as_ref(),
let wrapped_error = mlua_expect!(
get_gc_userdata::<WrappedError>(state, error_idx).as_mut(),
"cannot get <WrappedError>"
);
let traceback = if has_traceback != 0 {
let traceback = to_string(state, -1);
ffi::lua_pop(state, 1);
traceback
} else {
"<not enough stack space for traceback>".to_owned()
let traceback = match traceback_idx {
0 => "<not enough stack space for traceback>".to_string(),
_ => to_string(state, traceback_idx),
};
let error = error.clone();
ffi::lua_remove(state, -2); // Remove original error
ptr::write(
error_ud as *mut WrappedError,
WrappedError(Error::CallbackError {
traceback,
cause: Arc::new(error),
}),
);
get_gc_metatable_for::<WrappedError>(state);
ffi::lua_setmetatable(state, -2);
let cause = Arc::new(wrapped_error.0.clone());
wrapped_error.0 = Error::CallbackError { traceback, cause };
}
// Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1.
@@ -475,6 +461,29 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(*const
// 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);
}
(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);
}
_ => {}
}
Ok(err_buf)
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_ref() {
if let Some(ref p) = (*panic).0 {
+29
View File
@@ -0,0 +1,29 @@
#![cfg(feature = "macros")]
use mlua::{chunk, Lua, Result};
#[test]
fn test_chunk_macro() -> Result<()> {
let lua = Lua::new();
let name = "Rustacean";
let table = vec![1];
let data = lua.create_table()?;
data.raw_set("num", 1)?;
lua.globals().set("g", 123)?;
lua.load(chunk! {
assert($name == "Rustacean")
assert($table[1] == 1)
assert($data.num == 1)
assert(g == 123)
s = 321
})
.exec()?;
assert_eq!(lua.globals().get::<_, i32>("s")?, 321);
Ok(())
}
+43
View File
@@ -93,6 +93,49 @@ fn test_thread() -> Result<()> {
Ok(())
}
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
#[test]
fn test_thread_reset() -> Result<()> {
use mlua::{AnyUserData, UserData};
use std::sync::Arc;
let lua = Lua::new();
struct MyUserData(Arc<()>);
impl UserData for MyUserData {}
let arc = Arc::new(());
let func: Function = lua.load(r#"function(ud) coroutine.yield(ud) end"#).eval()?;
let thread = lua.create_thread(func.clone())?;
for _ in 0..2 {
assert_eq!(thread.status(), ThreadStatus::Resumable);
let _ = thread.resume::<_, AnyUserData>(MyUserData(arc.clone()))?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert_eq!(Arc::strong_count(&arc), 2);
thread.resume::<_, ()>(())?;
assert_eq!(thread.status(), ThreadStatus::Unresumable);
thread.reset(func.clone())?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&arc), 1);
}
// Check for errors (Lua 5.4 only)
#[cfg(feature = "lua54")]
{
let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?;
let thread = lua.create_thread(func.clone())?;
let _ = thread.resume::<_, AnyUserData>(MyUserData(arc.clone()));
assert_eq!(thread.status(), ThreadStatus::Error);
assert_eq!(Arc::strong_count(&arc), 2);
assert!(thread.reset(func.clone()).is_err());
assert_eq!(thread.status(), ThreadStatus::Error);
}
Ok(())
}
#[test]
fn coroutine_from_closure() -> Result<()> {
let lua = Lua::new();