diff options
author | Alex Orlenko <zxteam@protonmail.com> | 2022-03-01 12:29:43 +0000 |
---|---|---|
committer | Alex Orlenko <zxteam@protonmail.com> | 2022-03-21 01:08:47 +0000 |
commit | 32124b31a05a25c0146864661f3a34a8abb1a7d6 (patch) | |
tree | ea34992510c0553a434793b01abcfa37db5e288c /src | |
parent | 3e5f8e7bb8bb2822629593ecbd316f0fda7b4de8 (diff) | |
download | mlua-32124b31a05a25c0146864661f3a34a8abb1a7d6.zip |
Move chunks structs to a new module.
Add Luau Compiler interface to compile sources.
Diffstat (limited to 'src')
-rw-r--r-- | src/chunk.rs | 397 | ||||
-rw-r--r-- | src/ffi/luau/compat.rs | 2 | ||||
-rw-r--r-- | src/ffi/luau/luacode.rs | 21 | ||||
-rw-r--r-- | src/lib.rs | 7 | ||||
-rw-r--r-- | src/lua.rs | 227 |
5 files changed, 429 insertions, 225 deletions
diff --git a/src/chunk.rs b/src/chunk.rs new file mode 100644 index 0000000..3bb7c18 --- /dev/null +++ b/src/chunk.rs @@ -0,0 +1,397 @@ +use std::borrow::Cow; +use std::ffi::CString; + +use crate::error::{Error, Result}; +use crate::ffi; +use crate::function::Function; +use crate::lua::Lua; +use crate::value::{FromLuaMulti, ToLua, ToLuaMulti, Value}; + +#[cfg(feature = "async")] +use {futures_core::future::LocalBoxFuture, futures_util::future}; + +/// Trait for types [loadable by Lua] and convertible to a [`Chunk`] +/// +/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2 +/// [`Chunk`]: crate::Chunk +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.4/manual.html#2.2 + fn env(&self, _lua: &'lua Lua) -> Result<Option<Value<'lua>>> { + Ok(None) + } + + /// Returns optional chunk mode (text or binary) + fn mode(&self) -> Option<ChunkMode> { + None + } +} + +impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T { + fn source(&self) -> &[u8] { + self.as_ref() + } +} + +/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks. +/// +/// [`Lua::load`]: crate::Lua::load +#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] +pub struct Chunk<'lua, 'a> { + pub(crate) lua: &'lua Lua, + pub(crate) source: Cow<'a, [u8]>, + pub(crate) name: Option<CString>, + pub(crate) env: Result<Option<Value<'lua>>>, + pub(crate) mode: Option<ChunkMode>, + #[cfg(feature = "luau")] + pub(crate) compiler: Option<Compiler>, +} + +/// Represents chunk mode (text or binary). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ChunkMode { + Text, + Binary, +} + +/// Luau compiler +#[cfg(feature = "luau")] +#[derive(Clone, Copy, Debug)] +pub struct Compiler { + optimization_level: u8, + debug_level: u8, + coverage_level: u8, +} + +#[cfg(feature = "luau")] +impl Default for Compiler { + fn default() -> Self { + // Defaults are taken from luacode.h + Compiler { + optimization_level: 1, + debug_level: 1, + coverage_level: 0, + } + } +} + +#[cfg(feature = "luau")] +impl Compiler { + /// Creates Luau compiler instance with default options + pub fn new() -> Self { + Compiler::default() + } + + /// Sets Luau compiler optimization level. + /// + /// Possible values: + /// 0 - no optimization + /// 1 - baseline optimization level that doesn't prevent debuggability (default) + /// 2 - includes optimizations that harm debuggability such as inlining + #[cfg(feature = "luau")] + pub fn set_optimization_level(mut self, level: u8) -> Self { + self.optimization_level = level; + self + } + + /// Sets Luau compiler debug level. + /// + /// Possible values: + /// 0 - no debugging support + /// 1 - line info & function names only; sufficient for backtraces (default) + /// 2 - full debug info with local & upvalue names; necessary for debugger + #[cfg(feature = "luau")] + pub fn set_debug_level(mut self, level: u8) -> Self { + self.debug_level = level; + self + } + + /// Sets Luau compiler code coverage level. + /// + /// Possible values: + /// 0 - no code coverage support (default) + /// 1 - statement coverage + /// 2 - statement and expression coverage (verbose) + #[cfg(feature = "luau")] + pub fn set_coverage_level(mut self, level: u8) -> Self { + self.coverage_level = level; + self + } + + /// Compiles the `source` into bytecode. + pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> { + use std::os::raw::c_int; + use std::ptr; + + unsafe { + let options = ffi::lua_CompileOptions { + optimizationLevel: self.optimization_level as c_int, + debugLevel: self.debug_level as c_int, + coverageLevel: self.coverage_level as c_int, + vectorLib: ptr::null(), + vectorCtor: ptr::null(), + mutableGlobals: ptr::null_mut(), + }; + ffi::luau_compile(source.as_ref(), options) + } + } +} + +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>> { + let name = + CString::new(name.as_ref().to_vec()).map_err(|e| Error::ToLuaConversionError { + from: "&str", + to: "string", + message: Some(e.to_string()), + })?; + self.name = Some(name); + Ok(self) + } + + /// Sets the first upvalue (`_ENV`) of the loaded chunk to the given value. + /// + /// Lua main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV` + /// variable inside the chunk. By default this value is set to the global environment. + /// + /// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside + /// the chunk will refer to the given environment rather than the global one. + /// + /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be + /// 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>> { + // Prefer to propagate errors here and wrap to `Ok` + self.env = Ok(Some(env.to_lua(self.lua)?)); + Ok(self) + } + + /// Sets whether the chunk is text or binary (autodetected by default). + /// + /// Be aware, Lua does not check the consistency of the code inside binary chunks. + /// Running maliciously crafted bytecode can crash the interpreter. + pub fn set_mode(mut self, mode: ChunkMode) -> Chunk<'lua, 'a> { + self.mode = Some(mode); + self + } + + /// Sets Luau compiler optimization level. + /// + /// See [`Compiler::set_optimization_level`] for details. + /// + /// Requires `feature = "luau` + #[cfg(feature = "luau")] + pub fn set_optimization_level(mut self, level: u8) -> Self { + self.compiler + .get_or_insert_with(Default::default) + .set_optimization_level(level); + self + } + + /// Sets Luau compiler debug level. + /// + /// See [`Compiler::set_debug_level`] for details. + /// + /// Requires `feature = "luau` + #[cfg(feature = "luau")] + pub fn set_debug_level(mut self, level: u8) -> Self { + self.compiler + .get_or_insert_with(Default::default) + .set_debug_level(level); + self + } + + /// Sets Luau compiler code coverage level. + /// + /// See [`Compiler::set_coverage_level`] for details. + /// + /// Requires `feature = "luau` + #[cfg(feature = "luau")] + pub fn set_coverage_level(mut self, level: u8) -> Self { + self.compiler + .get_or_insert_with(Default::default) + .set_coverage_level(level); + self + } + + /// Compiles the chunk and changes mode to binary. + /// + /// It does nothing if the chunk is already binary. + #[cfg(feature = "luau")] + #[doc(hidden)] + pub fn compile(mut self) -> Self { + if self.detect_mode() == ChunkMode::Text { + let data = self + .compiler + .unwrap_or_default() + .compile(self.source.as_ref()); + self.mode = Some(ChunkMode::Binary); + self.source = Cow::Owned(data); + } + self + } + + /// Execute this chunk of code. + /// + /// This is equivalent to calling the chunk function with no arguments and no return values. + pub fn exec(self) -> Result<()> { + self.call(())?; + Ok(()) + } + + /// Asynchronously execute this chunk of code. + /// + /// See [`exec`] for more details. + /// + /// Requires `feature = "async"` + /// + /// [`exec`]: #method.exec + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>> + where + 'lua: 'fut, + { + self.call_async(()) + } + + /// Evaluate the chunk as either an expression or block. + /// + /// If the chunk can be parsed as an expression, this loads and executes the chunk and returns + /// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal, + /// and this is equivalent to calling `exec`. + pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> { + // Bytecode is always interpreted as a statement. + // For source code, first try interpreting the lua as an expression by adding + // "return", then as a statement. This is the same thing the + // actual lua repl does. + if self.detect_mode() == ChunkMode::Binary { + self.call(()) + } else if let Ok(function) = self.to_expression() { + function.call(()) + } else { + self.call(()) + } + } + + /// Asynchronously evaluate the chunk as either an expression or block. + /// + /// See [`eval`] for more details. + /// + /// Requires `feature = "async"` + /// + /// [`eval`]: #method.eval + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub fn eval_async<'fut, R>(self) -> LocalBoxFuture<'fut, Result<R>> + where + 'lua: 'fut, + R: FromLuaMulti<'lua> + 'fut, + { + if self.detect_mode() == ChunkMode::Binary { + self.call_async(()) + } else if let Ok(function) = self.to_expression() { + function.call_async(()) + } else { + self.call_async(()) + } + } + + /// 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 arguments. + /// + /// See [`call`] for more details. + /// + /// Requires `feature = "async"` + /// + /// [`call`]: #method.call + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>> + where + 'lua: 'fut, + A: ToLuaMulti<'lua>, + R: FromLuaMulti<'lua> + 'fut, + { + match self.into_function() { + Ok(func) => func.call_async(args), + Err(e) => Box::pin(future::err(e)), + } + } + + /// Load this chunk into a regular `Function`. + /// + /// This simply compiles the chunk without actually executing it. + pub fn into_function(self) -> Result<Function<'lua>> { + #[cfg(not(feature = "luau"))] + let self_ = self; + #[cfg(feature = "luau")] + let self_ = match self.compiler { + // We don't need to compile source if no compiler options set + Some(_) => self.compile(), + _ => self, + }; + + self_.lua.load_chunk( + self_.source.as_ref(), + self_.name.as_ref(), + self_.env()?, + self_.mode, + ) + } + + fn env(&self) -> Result<Option<Value<'lua>>> { + self.env.clone() + } + + fn expression_source(&self) -> Vec<u8> { + let mut buf = Vec::with_capacity(b"return ".len() + self.source.len()); + buf.extend(b"return "); + buf.extend(self.source.as_ref()); + buf + } + + fn to_expression(&self) -> Result<Function<'lua>> { + // We assume that mode is Text + let source = self.expression_source(); + // We don't need to compile source if no compiler options set + #[cfg(feature = "luau")] + let source = self.compiler.map(|c| c.compile(&source)).unwrap_or(source); + + self.lua + .load_chunk(&source, self.name.as_ref(), self.env()?, None) + } + + fn detect_mode(&self) -> ChunkMode { + match self.mode { + Some(mode) => mode, + None => { + #[cfg(not(feature = "luau"))] + if self.source.starts_with(ffi::LUA_SIGNATURE) { + return ChunkMode::Binary; + } + #[cfg(feature = "luau")] + if self.source[0] < b'\n' { + return ChunkMode::Binary; + } + ChunkMode::Text + } + } + } +} diff --git a/src/ffi/luau/compat.rs b/src/ffi/luau/compat.rs index 9094822..db9c55c 100644 --- a/src/ffi/luau/compat.rs +++ b/src/ffi/luau/compat.rs @@ -380,7 +380,7 @@ pub unsafe fn luaL_loadbufferx( } if chunk_is_text { - let data = luau_compile(data, size, ptr::null_mut(), &mut size); + let data = luau_compile_(data, size, ptr::null_mut(), &mut size); let ok = luau_load(L, name, data, size, 0) == 0; free(data as *mut c_void); if !ok { diff --git a/src/ffi/luau/luacode.rs b/src/ffi/luau/luacode.rs index 3f24b65..571db4c 100644 --- a/src/ffi/luau/luacode.rs +++ b/src/ffi/luau/luacode.rs @@ -1,6 +1,7 @@ //! Contains definitions from `luacode.h`. -use std::os::raw::{c_char, c_int}; +use std::os::raw::{c_char, c_int, c_void}; +use std::slice; #[repr(C)] pub struct lua_CompileOptions { @@ -13,10 +14,26 @@ pub struct lua_CompileOptions { } extern "C" { - pub fn luau_compile( + #[link_name = "luau_compile"] + pub fn luau_compile_( source: *const c_char, size: usize, options: *mut lua_CompileOptions, outsize: *mut usize, ) -> *mut c_char; + + fn free(p: *mut c_void); +} + +pub unsafe fn luau_compile(source: &[u8], mut options: lua_CompileOptions) -> Vec<u8> { + let mut outsize = 0; + let data_ptr = luau_compile_( + source.as_ptr() as *const c_char, + source.len(), + &mut options, + &mut outsize, + ); + let data = slice::from_raw_parts(data_ptr as *mut u8, outsize).to_vec(); + free(data_ptr as *mut c_void); + data } @@ -81,6 +81,7 @@ #[macro_use] mod macros; +mod chunk; mod conversion; mod error; mod ffi; @@ -102,10 +103,11 @@ pub mod prelude; pub use crate::{ffi::lua_CFunction, ffi::lua_State}; +pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::error::{Error, ExternalError, ExternalResult, Result}; pub use crate::function::Function; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; -pub use crate::lua::{AsChunk, Chunk, ChunkMode, GCMode, Lua, LuaOptions}; +pub use crate::lua::{GCMode, Lua, LuaOptions}; pub use crate::multi::Variadic; pub use crate::scope::Scope; pub use crate::stdlib::StdLib; @@ -121,6 +123,9 @@ pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti #[cfg(not(feature = "luau"))] pub use crate::hook::HookTriggers; +#[cfg(feature = "luau")] +pub use crate::chunk::Compiler; + #[cfg(feature = "async")] pub use crate::thread::AsyncThread; @@ -1,4 +1,5 @@ use std::any::{Any, TypeId}; +use std::borrow::Cow; use std::cell::{Ref, RefCell, RefMut, UnsafeCell}; use std::collections::HashMap; use std::ffi::CString; @@ -11,6 +12,7 @@ use std::{mem, ptr, str}; use rustc_hash::FxHashMap; +use crate::chunk::{AsChunk, Chunk, ChunkMode}; use crate::error::{Error, Result}; use crate::ffi; use crate::function::Function; @@ -1145,17 +1147,19 @@ impl Lua { { Chunk { lua: self, - source: source.source(), + source: Cow::Borrowed(source.source()), name: match source.name() { Some(name) => Some(name), None => CString::new(Location::caller().to_string()).ok(), }, env: source.env(self), mode: source.mode(), + #[cfg(feature = "luau")] + compiler: None, } } - fn load_chunk<'lua>( + pub(crate) fn load_chunk<'lua>( &'lua self, source: &[u8], name: Option<&CString>, @@ -2705,225 +2709,6 @@ impl Lua { } } -/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks. -/// -/// [`Lua::load`]: crate::Lua::load -#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] -pub struct Chunk<'lua, 'a> { - lua: &'lua Lua, - source: &'a [u8], - name: Option<CString>, - env: Result<Option<Value<'lua>>>, - mode: Option<ChunkMode>, -} - -/// Represents chunk mode (text or binary). -#[derive(Clone, Copy, Debug)] -pub enum ChunkMode { - Text, - Binary, -} - -/// Trait for types [loadable by Lua] and convertible to a [`Chunk`] -/// -/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2 -/// [`Chunk`]: crate::Chunk -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.4/manual.html#2.2 - fn env(&self, _lua: &'lua Lua) -> Result<Option<Value<'lua>>> { - Ok(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>> { - let name = - CString::new(name.as_ref().to_vec()).map_err(|e| Error::ToLuaConversionError { - from: "&str", - to: "string", - message: Some(e.to_string()), - })?; - self.name = Some(name); - Ok(self) - } - - /// Sets the first upvalue (`_ENV`) of the loaded chunk to the given value. - /// - /// Lua main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV` - /// variable inside the chunk. By default this value is set to the global environment. - /// - /// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside - /// the chunk will refer to the given environment rather than the global one. - /// - /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be - /// 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>> { - // Prefer to propagate errors here and wrap to `Ok` - self.env = Ok(Some(env.to_lua(self.lua)?)); - Ok(self) - } - - /// Sets whether the chunk is text or binary (autodetected by default). - /// - /// Lua does not check the consistency of binary chunks, therefore this mode is allowed only - /// for instances created with [`Lua::unsafe_new`]. - /// - /// [`Lua::unsafe_new`]: crate::Lua::unsafe_new - pub fn set_mode(mut self, mode: ChunkMode) -> Chunk<'lua, 'a> { - self.mode = Some(mode); - self - } - - /// Execute this chunk of code. - /// - /// This is equivalent to calling the chunk function with no arguments and no return values. - pub fn exec(self) -> Result<()> { - self.call(())?; - Ok(()) - } - - /// Asynchronously execute this chunk of code. - /// - /// See [`exec`] for more details. - /// - /// Requires `feature = "async"` - /// - /// [`exec`]: #method.exec - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>> - where - 'lua: 'fut, - { - self.call_async(()) - } - - /// Evaluate the chunk as either an expression or block. - /// - /// If the chunk can be parsed as an expression, this loads and executes the chunk and returns - /// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal, - /// and this is equivalent to calling `exec`. - pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> { - // Bytecode is always interpreted as a statement. - // For source code, first try interpreting the lua as an expression by adding - // "return", then as a statement. This is the same thing the - // actual lua repl does. - if self.source[0] < b'\n' { - self.call(()) - } else if let Ok(function) = self.lua.load_chunk( - &self.expression_source(), - self.name.as_ref(), - self.env()?, - self.mode, - ) { - function.call(()) - } else { - self.call(()) - } - } - - /// Asynchronously evaluate the chunk as either an expression or block. - /// - /// See [`eval`] for more details. - /// - /// Requires `feature = "async"` - /// - /// [`eval`]: #method.eval - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn eval_async<'fut, R>(self) -> LocalBoxFuture<'fut, Result<R>> - where - 'lua: 'fut, - R: FromLuaMulti<'lua> + 'fut, - { - if self.source[0] < b'\n' { - self.call_async(()) - } else if let Ok(function) = self.lua.load_chunk( - &self.expression_source(), - self.name.as_ref(), - match self.env() { - Ok(env) => env, - Err(e) => return Box::pin(future::err(e)), - }, - self.mode, - ) { - function.call_async(()) - } else { - self.call_async(()) - } - } - - /// 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 arguments. - /// - /// See [`call`] for more details. - /// - /// Requires `feature = "async"` - /// - /// [`call`]: #method.call - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>> - where - 'lua: 'fut, - A: ToLuaMulti<'lua>, - R: FromLuaMulti<'lua> + 'fut, - { - match self.into_function() { - Ok(func) => func.call_async(args), - Err(e) => Box::pin(future::err(e)), - } - } - - /// Load this chunk into a regular `Function`. - /// - /// 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) - } - - fn env(&self) -> Result<Option<Value<'lua>>> { - self.env.clone() - } - - fn expression_source(&self) -> Vec<u8> { - let mut buf = Vec::with_capacity(b"return ".len() + self.source.len()); - buf.extend(b"return "); - buf.extend(self.source); - buf - } -} - -impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T { - fn source(&self) -> &[u8] { - self.as_ref() - } -} - // Creates required entries in the metatable cache (see `util::METATABLE_CACHE`) pub(crate) fn init_metatable_cache(cache: &mut FxHashMap<TypeId, u8>) { cache.insert(TypeId::of::<Arc<UnsafeCell<ExtraData>>>(), 0); |