use std::ffi::c_void;
use std::ptr::null_mut;
pub mod async_thread;
pub mod call_info;
mod const_string;
pub mod debug_info;
mod error_msg;
mod execute;
mod file_layout;
pub mod lua_error;
pub mod lua_limits;
mod lua_ref;
mod lua_rng;
mod lua_state;
pub mod opcode;
mod safe_option;
#[cfg(feature = "sandbox")]
mod sandbox;
#[cfg(feature = "shared-proto")]
mod shared_proto;
pub(crate) mod stk_id;
mod string_arth;
use crate::compiler::{LuaLanguageLevel, compile_code, compile_code_with_name};
use crate::gc::{
CreateResult, GcKind, GcObjectPtr, GcState, ObjectAllocator, ThreadPtr, UpvaluePtr,
};
use crate::gc::{GC, ProtoPtr};
use crate::lua_value::lua_convert::{FromLua, IntoLua};
use crate::lua_value::{LuaProto, LuaUpvalue, LuaUserdata, LuaValue, LuaValueKind, UpvalueStore};
pub use crate::lua_vm::call_info::{CallInfo, CallInfoPtr};
use crate::lua_vm::const_string::ConstString;
pub use crate::lua_vm::debug_info::DebugInfo;
pub(crate) use crate::lua_vm::error_msg::ErrorMsg;
use crate::lua_vm::file_layout::inspect_file_chunk_layout;
pub use crate::lua_vm::lua_error::LuaError;
use crate::lua_vm::lua_ref::RefManager;
use crate::lua_vm::lua_ref::store_in_registry;
pub use crate::lua_vm::lua_ref::{
LUA_REFNIL, LuaAnyRef, LuaFunctionRef, LuaRefValue, LuaStringRef, LuaTableRef, RefId,
UserDataRef,
};
pub(crate) use crate::lua_vm::stk_id::StkId;
type ArithMetaFn = fn(&mut LuaState) -> LuaResult<usize>;
pub use crate::lua_vm::lua_state::LuaState;
pub use crate::lua_vm::safe_option::SafeOption;
#[cfg(feature = "sandbox")]
pub use crate::lua_vm::sandbox::SandboxConfig;
use crate::platform_time::{PlatformInstant, unix_nanos};
use crate::stdlib::Stdlib;
use crate::{LuaEnum, LuaRegistrable, OpaqueUserData, RustCallback, lib_registry};
pub use execute::TmKind;
pub(crate) use execute::arith::{lua_shiftl, luai_numpow};
pub use execute::{get_metamethod_event, get_metatable};
pub use lua_rng::LuaRng;
pub use opcode::{Instruction, OpCode};
use std::future::Future;
use std::pin::Pin;
use std::ptr::NonNull;
pub use string_arth::*;
pub type LuaResult<T> = Result<T, LuaError>;
pub type CFunction = fn(&mut LuaState) -> LuaResult<usize>;
#[doc(hidden)]
pub trait LuaTypedCallback<Args, R>: 'static {
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize>;
}
#[doc(hidden)]
pub trait LuaTypedAsyncCallback<Args, R>: 'static {
fn invoke_typed_async(&self, state: &mut LuaState) -> LuaResult<async_thread::AsyncFuture>;
}
fn typed_callback_arg<T: FromLua>(state: &mut LuaState, index: usize) -> LuaResult<T> {
let value = state.get_arg(index).unwrap_or_default();
match T::from_lua(value, state) {
Ok(value) => Ok(value),
Err(msg) => Err(state.error(msg)),
}
}
impl<Func, R> LuaTypedCallback<(), R> for Func
where
Func: Fn() -> R + 'static,
R: IntoLua,
{
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize> {
match (self)().into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
macro_rules! impl_lua_typed_callback {
($(($(($ty:ident, $value:ident) => $index:literal),+)),* $(,)?) => {
$(
impl<Func, R, $($ty),+> LuaTypedCallback<($($ty,)+), R> for Func
where
Func: Fn($($ty),+) -> R + 'static,
R: IntoLua,
$($ty: FromLua),+
{
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize> {
$(
let $value = typed_callback_arg::<$ty>(state, $index)?;
)+
match (self)($($value),+).into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
)*
};
}
impl_lua_typed_callback!(
((A, a) => 1),
((A, a) => 1, (B, b) => 2),
((A, a) => 1, (B, b) => 2, (C, c) => 3),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7, (T8, t8) => 8)
);
impl<Func, Fut, R> LuaTypedAsyncCallback<(), R> for Func
where
Func: Fn() -> Fut + 'static,
Fut: Future<Output = LuaResult<R>> + 'static,
R: async_thread::IntoAsyncLua,
{
fn invoke_typed_async(&self, _state: &mut LuaState) -> LuaResult<async_thread::AsyncFuture> {
let future = (self)();
Ok(Box::pin(async move {
let value = future.await?;
Ok(value.into_async_lua())
}))
}
}
macro_rules! impl_lua_typed_async_callback {
($(($(($ty:ident, $value:ident) => $index:literal),+)),* $(,)?) => {
$(
impl<Func, Fut, R, $($ty),+> LuaTypedAsyncCallback<($($ty,)+), R> for Func
where
Func: Fn($($ty),+) -> Fut + 'static,
Fut: Future<Output = LuaResult<R>> + 'static,
R: async_thread::IntoAsyncLua,
$($ty: FromLua),+
{
fn invoke_typed_async(&self, state: &mut LuaState) -> LuaResult<async_thread::AsyncFuture> {
$(
let $value = typed_callback_arg::<$ty>(state, $index)?;
)+
let future = (self)($($value),+);
Ok(Box::pin(async move {
let value = future.await?;
Ok(value.into_async_lua())
}))
}
}
)*
};
}
impl_lua_typed_async_callback!(
((A, a) => 1),
((A, a) => 1, (B, b) => 2),
((A, a) => 1, (B, b) => 2, (C, c) => 3),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7, (T8, t8) => 8)
);
pub const LUA_HOOKCALL: i32 = 0;
pub const LUA_HOOKRET: i32 = 1;
pub const LUA_HOOKLINE: i32 = 2;
pub const LUA_HOOKCOUNT: i32 = 3;
pub const LUA_HOOKTAILCALL: i32 = 4;
pub const LUA_MASKCALL: u8 = 1 << LUA_HOOKCALL as u8;
pub const LUA_MASKRET: u8 = 1 << LUA_HOOKRET as u8;
pub const LUA_MASKLINE: u8 = 1 << LUA_HOOKLINE as u8;
pub const LUA_MASKCOUNT: u8 = 1 << LUA_HOOKCOUNT as u8;
pub struct GlobalState {
pub(crate) global: LuaValue,
pub(crate) registry: LuaValue,
pub(crate) ref_manager: RefManager,
pub(crate) object_allocator: ObjectAllocator,
pub(crate) gc: GC,
pub(crate) main_state: ThreadPtr,
pub(crate) string_mt: Option<LuaValue>,
pub(crate) number_mt: Option<LuaValue>,
pub(crate) bool_mt: Option<LuaValue>,
pub(crate) nil_mt: Option<LuaValue>,
pub(crate) safe_option: SafeOption,
pub(crate) n_ccalls: usize,
pub(crate) version: LuaLanguageLevel,
pub(crate) rng: LuaRng,
pub(crate) start_time: PlatformInstant,
pub(crate) const_strings: ConstString,
pub(crate) error_msg: ErrorMsg,
pub(crate) extra_space: *mut c_void,
pub(crate) io_default_output: Option<LuaValue>,
pub(crate) io_default_input: Option<LuaValue>,
}
impl GlobalState {
pub fn new(option: SafeOption) -> Pin<Box<Self>> {
let mut gc = GC::new(option.clone());
gc.set_temporary_memory_limit(isize::MAX / 2);
let mut object_allocator = ObjectAllocator::new();
#[cfg(feature = "shared-proto")]
let cs = shared_proto::get_or_init_const_strings(&mut object_allocator, &mut gc);
#[cfg(not(feature = "shared-proto"))]
let cs = ConstString::new(&mut object_allocator, &mut gc);
let time = unix_nanos();
let mut inner = Box::pin(GlobalState {
global: LuaValue::nil(),
registry: LuaValue::nil(),
ref_manager: RefManager::new(),
object_allocator,
gc,
main_state: ThreadPtr::null(), string_mt: None,
number_mt: None,
bool_mt: None,
nil_mt: None,
safe_option: option.clone(),
n_ccalls: 0,
version: LuaLanguageLevel::Lua55,
rng: LuaRng::from_seed_time(time),
start_time: PlatformInstant::now(),
const_strings: cs,
error_msg: ErrorMsg::None,
extra_space: null_mut(),
io_default_output: None,
io_default_input: None,
});
let thread_value = {
let state = unsafe { inner.as_mut().get_unchecked_mut() };
let vm_handle = GlobalStateHandle::from_global(state);
let allocator = &mut state.object_allocator as *mut ObjectAllocator;
let gc = &mut state.gc as *mut GC;
unsafe {
(*allocator)
.create_thread(&mut *gc, LuaState::new(6, vm_handle, true, option.clone()))
}
.unwrap()
};
inner.main_state = thread_value.as_thread_ptr().unwrap();
let registry = inner.create_table(2, 8).unwrap();
inner.registry = registry;
let globals_value = inner.create_table(0, 20).unwrap();
inner.global = globals_value;
inner.set_global("_G", globals_value).unwrap();
inner.set_global("_ENV", globals_value).unwrap();
inner.gc.clear_temporary_memory_limit();
inner
}
pub(crate) fn main_state(&mut self) -> &mut LuaState {
&mut self.main_state.as_mut_ref().data
}
#[cold]
#[inline(never)]
pub fn error(&mut self, msg: String) -> LuaError {
self.error_msg = ErrorMsg::Msg(msg);
LuaError::RuntimeError
}
#[cold]
#[inline(never)]
pub fn error_with_object(&mut self, obj: LuaValue) -> LuaError {
self.error_msg = ErrorMsg::Object(obj);
LuaError::RuntimeError
}
#[inline(always)]
pub fn take_error(&mut self) -> ErrorMsg {
std::mem::take(&mut self.error_msg)
}
#[inline]
pub(crate) fn get_error_object_ref(&self) -> Option<&LuaValue> {
if let ErrorMsg::Object(ref obj) = self.error_msg {
Some(obj)
} else {
None
}
}
#[inline]
pub fn set_extra_space(&mut self, pointer: *mut c_void) {
self.extra_space = pointer;
}
#[inline]
pub fn extra_space(&self) -> *mut c_void {
self.extra_space
}
pub fn register_preload(&mut self, name: &str, loader: CFunction) -> LuaResult<()> {
let preload_val = self.registry_get("_PRELOAD")?;
if let Some(preload) = preload_val
&& preload.is_table()
{
let key = self.create_string(name)?;
self.raw_set(&preload, key, LuaValue::cfunction(loader));
}
Ok(())
}
pub fn registry_seti(&mut self, key: i64, value: LuaValue) {
self.raw_seti(&self.registry.clone(), key, value);
}
pub fn registry_geti(&self, key: i64) -> Option<LuaValue> {
self.raw_geti(&self.registry, key)
}
pub fn registry_set(&mut self, key: &str, value: LuaValue) -> LuaResult<()> {
let key_value = self.create_string(key)?;
let registry = self.registry;
self.raw_set(®istry, key_value, value);
Ok(())
}
pub fn registry_get(&mut self, key: &str) -> LuaResult<Option<LuaValue>> {
let key = self.create_string(key)?;
Ok(self.raw_get(&self.registry, &key))
}
pub fn create_ref(&mut self, value: LuaValue) -> LuaRefValue {
let ref_id = self.ref_manager.alloc_ref_id();
self.registry_seti(ref_id as i64, value);
LuaRefValue::new_registry(ref_id)
}
pub fn get_ref_value(&self, lua_ref: &LuaRefValue) -> LuaValue {
lua_ref.get(self)
}
pub fn release_ref(&mut self, lua_ref: LuaRefValue) {
let ref_id = lua_ref.ref_id();
if ref_id > 0 {
self.registry_seti(ref_id as i64, LuaValue::nil());
self.ref_manager.free_ref_id(ref_id);
}
}
pub fn release_ref_id(&mut self, ref_id: RefId) {
if ref_id > 0 {
self.registry_seti(ref_id as i64, LuaValue::nil());
self.ref_manager.free_ref_id(ref_id);
}
}
pub fn get_ref_value_by_id(&self, ref_id: RefId) -> LuaValue {
if ref_id == LUA_REFNIL {
return LuaValue::nil();
}
if ref_id <= 0 {
return LuaValue::nil();
}
self.registry_geti(ref_id as i64).unwrap_or_default()
}
pub fn open_stdlib(&mut self, lib: Stdlib) -> LuaResult<()> {
lib_registry::create_standard_registry(lib).load_all(self)?;
Ok(())
}
pub fn open_stdlibs(&mut self, libs: &[Stdlib]) -> LuaResult<()> {
for lib in libs {
self.open_stdlib(*lib)?;
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn serialize_to_json(&self, value: &LuaValue) -> Result<serde_json::Value, String> {
use crate::serde::lua_to_json;
lua_to_json(value)
}
#[cfg(feature = "serde")]
pub fn serialize_to_json_string(
&self,
value: &LuaValue,
pretty: bool,
) -> Result<String, String> {
use crate::serde::lua_to_json_string;
lua_to_json_string(value, pretty)
}
#[cfg(feature = "serde")]
pub fn deserialize_from_json(&mut self, json: &serde_json::Value) -> Result<LuaValue, String> {
use crate::serde::json_to_lua;
json_to_lua(json, self)
}
#[cfg(feature = "serde")]
pub fn deserialize_from_json_string(&mut self, json_str: &str) -> Result<LuaValue, String> {
use crate::serde::json_string_to_lua;
json_string_to_lua(json_str, self)
}
#[inline]
pub(crate) fn prepare_loaded_chunk(&mut self, chunk: LuaProto) -> LuaResult<ProtoPtr> {
#[cfg(feature = "shared-proto")]
{
use crate::gc::share_proto;
let proto = self.create_proto(chunk)?;
share_proto(proto);
Ok(proto)
}
#[cfg(not(feature = "shared-proto"))]
self.create_proto(chunk)
}
#[inline]
pub(crate) fn create_loaded_function(
&mut self,
chunk: LuaProto,
upvalues: UpvalueStore,
) -> LuaResult<LuaValue> {
let chunk = self.prepare_loaded_chunk(chunk)?;
self.create_function(chunk, upvalues)
}
pub fn register_function<F>(&mut self, name: &str, f: F) -> LuaResult<()>
where
F: Fn(&mut LuaState) -> LuaResult<usize> + 'static,
{
let closure_val = self.create_closure(f)?;
self.set_global(name, closure_val)
}
pub fn register_function_typed<F, Args, R>(&mut self, name: &str, f: F) -> LuaResult<()>
where
F: LuaTypedCallback<Args, R>,
{
self.register_function(name, move |state| f.invoke_typed(state))
}
pub fn create_function_typed<F, Args, R>(&mut self, f: F) -> LuaResult<LuaFunctionRef>
where
F: LuaTypedCallback<Args, R>,
{
let closure_val = self.create_closure(move |state| f.invoke_typed(state))?;
Ok(self.to_function_ref(closure_val).unwrap())
}
pub fn register_async_typed<F, Args, R>(&mut self, name: &str, f: F) -> LuaResult<()>
where
F: LuaTypedAsyncCallback<Args, R>,
{
let wrapper = move |state: &mut LuaState| {
let future = f.invoke_typed_async(state)?;
state.set_pending_future(future);
state.do_yield(vec![async_thread::async_sentinel_value()])?;
Ok(0)
};
let closure_val = self.create_closure(wrapper)?;
self.set_global(name, closure_val)
}
pub fn register_type_of<T: LuaRegistrable>(&mut self, name: &str) -> LuaResult<()> {
let static_methods = T::lua_static_methods();
let class_table = self.create_table(0, static_methods.len())?;
for &(method_name, func) in static_methods {
let key = self.create_string(method_name)?;
self.raw_set(&class_table, key, LuaValue::cfunction(func));
}
self.set_global(name, class_table)
}
pub fn create_table_ref(
&mut self,
array_size: usize,
hash_size: usize,
) -> LuaResult<LuaTableRef> {
let table = self.create_table(array_size, hash_size)?;
Ok(self.to_table_ref(table).unwrap())
}
pub fn get_global_table(&mut self, name: &str) -> LuaResult<Option<LuaTableRef>> {
match self.get_global(name)? {
Some(val) if val.is_table() => Ok(self.to_table_ref(val)),
_ => Ok(None),
}
}
pub fn globals_table(&mut self) -> LuaTableRef {
self.to_table_ref(self.global)
.expect("global environment must be a table")
}
pub fn get_global_function(&mut self, name: &str) -> LuaResult<Option<LuaFunctionRef>> {
match self.get_global(name)? {
Some(val) if val.is_function() => Ok(self.to_function_ref(val)),
_ => Ok(None),
}
}
pub fn create_any<T: 'static>(&mut self, value: T) -> LuaResult<LuaValue> {
let ud = LuaUserdata::new(OpaqueUserData::new(value));
self.create_userdata(ud)
}
pub fn to_ref(&mut self, value: LuaValue) -> LuaAnyRef {
let ref_id = store_in_registry(self, value);
LuaAnyRef::from_raw(ref_id, GlobalStateHandle::from_global(self))
}
pub fn to_table_ref(&mut self, value: LuaValue) -> Option<LuaTableRef> {
if !value.is_table() {
return None;
}
let ref_id = store_in_registry(self, value);
Some(LuaTableRef::from_raw(
ref_id,
GlobalStateHandle::from_global(self),
))
}
pub fn to_function_ref(&mut self, value: LuaValue) -> Option<LuaFunctionRef> {
if !value.is_function() {
return None;
}
let ref_id = store_in_registry(self, value);
Some(LuaFunctionRef::from_raw(
ref_id,
GlobalStateHandle::from_global(self),
))
}
pub fn to_string_ref(&mut self, value: LuaValue) -> Option<LuaStringRef> {
if !value.is_string() {
return None;
}
let ref_id = store_in_registry(self, value);
Some(LuaStringRef::from_raw(
ref_id,
GlobalStateHandle::from_global(self),
))
}
pub fn to_userdata_ref<T: 'static>(&mut self, value: LuaValue) -> Option<UserDataRef<T>> {
let userdata = value.as_userdata_mut()?;
userdata.downcast_ref::<T>()?;
let ref_id = store_in_registry(self, value);
Some(UserDataRef::from_raw(
ref_id,
GlobalStateHandle::from_global(self),
))
}
pub fn compile(&mut self, source: &str) -> Result<LuaProto, String> {
self.gc.disable_memory_check();
let chunk = match compile_code(source, self) {
Ok(c) => c,
Err(e) => {
self.gc.enable_memory_check();
return Err(e);
}
};
self.gc.enable_memory_check();
self.gc
.check_memory()
.map_err(|_| self.gc.get_error_message())?;
Ok(chunk)
}
pub fn compile_with_name(
&mut self,
source: &str,
chunk_name: &str,
) -> Result<LuaProto, String> {
self.gc.disable_memory_check();
let chunk = match compile_code_with_name(source, self, chunk_name) {
Ok(c) => c,
Err(e) => {
self.gc.enable_memory_check();
return Err(e);
}
};
self.gc.enable_memory_check();
self.gc
.check_memory()
.map_err(|_| self.gc.get_error_message())?;
Ok(chunk)
}
pub(crate) fn load_proto_from_file(&mut self, path: &str) -> Result<ProtoPtr, String> {
use crate::lua_value::chunk_serializer;
#[cfg(miri)]
let resolved_path = std::path::PathBuf::from(path);
#[cfg(not(miri))]
let resolved_path =
std::fs::canonicalize(path).map_err(|e| format!("cannot open {}: {}", path, e))?;
let file_bytes =
std::fs::read(&resolved_path).map_err(|e| format!("cannot open {}: {}", path, e))?;
let layout = inspect_file_chunk_layout(&file_bytes);
if layout.is_binary && !self.safe_option.allow_load_bytecode {
return Err(
"attempt to load a binary chunk (bytecode loading is disabled)".to_string(),
);
}
#[cfg(feature = "shared-proto")]
{
use crate::lua_vm::shared_proto::SHARED_FILE_PROTO_CACHE;
let metadata = std::fs::metadata(&resolved_path)
.map_err(|e| format!("cannot open {}: {}", path, e))?;
let len = metadata.len();
let modified = metadata.modified().ok();
let version = self.version;
if let Some(proto) = SHARED_FILE_PROTO_CACHE.with(|cache| {
let cache = cache.borrow();
cache.get(&resolved_path).and_then(|entry| {
(entry.len == len && entry.modified == modified && entry.version == version)
.then_some(entry.proto)
})
}) {
return Ok(proto);
}
}
let chunk_name = format!("@{}", resolved_path.display());
let chunk = if layout.is_binary {
chunk_serializer::deserialize_chunk_with_strings_vm(
&file_bytes[layout.skip_offset..],
self,
)
.map_err(|e| format!("binary load error: {}", e))?
} else {
let code_str = String::from_utf8(file_bytes[layout.text_start..].to_vec())
.map_err(|_| "source file is not valid UTF-8".to_string())?;
self.compile_with_name(&code_str, &chunk_name)?
};
let proto = self
.prepare_loaded_chunk(chunk)
.map_err(|_| self.gc.get_error_message())?;
#[cfg(feature = "shared-proto")]
{
use crate::lua_vm::shared_proto::SHARED_FILE_PROTO_CACHE;
let metadata = std::fs::metadata(&resolved_path)
.map_err(|e| format!("cannot open {}: {}", path, e))?;
SHARED_FILE_PROTO_CACHE.with(|cache| {
use crate::lua_vm::shared_proto::SharedFileProtoEntry;
cache.borrow_mut().insert(
resolved_path,
SharedFileProtoEntry {
proto,
len: metadata.len(),
modified: metadata.modified().ok(),
version: self.version,
},
);
});
}
Ok(proto)
}
pub fn get_global(&mut self, name: &str) -> LuaResult<Option<LuaValue>> {
let key = self.create_string(name)?;
Ok(self.raw_get(&self.global, &key))
}
pub fn set_global(&mut self, name: &str, value: LuaValue) -> LuaResult<()> {
let key = self.create_string(name)?;
let global = self.global;
self.raw_set(&global, key, value);
Ok(())
}
#[cfg(feature = "sandbox")]
pub fn create_sandbox_env(&mut self, config: &SandboxConfig) -> LuaResult<LuaValue> {
use crate::lua_vm::sandbox::SANDBOX_LIB_GLOBALS;
let env = self.create_table(0, 24)?;
let g_key = self.create_string("_G")?;
let env_key = self.create_string("_ENV")?;
self.copy_global_into_table(&env, "_G")?;
self.copy_global_into_table(&env, "_ENV")?;
self.raw_set(&env, g_key, env);
self.raw_set(&env, env_key, env);
if config.basic {
use crate::lua_vm::sandbox::SANDBOX_SAFE_BASIC_GLOBALS;
for &name in SANDBOX_SAFE_BASIC_GLOBALS {
self.copy_global_into_table(&env, name)?;
}
if config.allow_require {
self.copy_global_into_table(&env, "require")?;
}
if config.allow_load {
self.copy_global_into_table(&env, "load")?;
}
if config.allow_loadfile {
self.copy_global_into_table(&env, "loadfile")?;
}
if config.allow_dofile {
self.copy_global_into_table(&env, "dofile")?;
}
if config.allow_collectgarbage {
self.copy_global_into_table(&env, "collectgarbage")?;
}
}
for &(lib, global_name) in SANDBOX_LIB_GLOBALS {
let enabled = match lib {
Stdlib::Math => config.math,
Stdlib::String => config.string,
Stdlib::Table => config.table,
Stdlib::Utf8 => config.utf8,
Stdlib::Coroutine => config.coroutine,
Stdlib::Os => config.os,
Stdlib::Io => config.io,
Stdlib::Package => config.package,
Stdlib::Debug => config.debug,
Stdlib::Basic | Stdlib::All => false,
};
if enabled {
self.copy_global_into_table(&env, global_name)?;
}
}
for (name, value) in &config.injected_globals {
let key = self.create_string(name)?;
self.raw_set(&env, key, *value);
}
Ok(env)
}
#[cfg(feature = "sandbox")]
fn copy_global_into_table(&mut self, table: &LuaValue, name: &str) -> LuaResult<()> {
let key = self.create_string(name)?;
if let Some(value) = self.raw_get(&self.global, &key) {
self.raw_set(table, key, value);
}
Ok(())
}
pub fn set_string_metatable(&mut self, string_lib_table: LuaValue) -> LuaResult<()> {
let mt_value = self.create_table(0, 10)?;
let index_key = self
.const_strings
.get_tm_value(crate::lua_vm::TmKind::Index);
self.raw_set(&mt_value, index_key, string_lib_table);
use crate::lua_vm::TmKind;
let arith_metas: &[(TmKind, ArithMetaFn)] = &[
(TmKind::Add, string_arith_add),
(TmKind::Sub, string_arith_sub),
(TmKind::Mul, string_arith_mul),
(TmKind::Mod, string_arith_mod),
(TmKind::Pow, string_arith_pow),
(TmKind::Div, string_arith_div),
(TmKind::IDiv, string_arith_idiv),
(TmKind::Unm, string_arith_unm),
];
for &(tm, func) in arith_metas {
let key = self.const_strings.get_tm_value(tm);
self.raw_set(&mt_value, key, LuaValue::cfunction(func));
}
self.string_mt = Some(mt_value);
Ok(())
}
pub fn create_thread(&mut self, func: LuaValue) -> CreateResult {
let mut thread = LuaState::new(
1,
GlobalStateHandle::from_global(self),
false,
self.safe_option.clone(),
);
thread
.push_value(func)
.expect("Failed to push function onto coroutine stack");
self.object_allocator.create_thread(&mut self.gc, thread)
}
pub fn create_empty_thread(&mut self) -> CreateResult {
let thread = LuaState::new(
1,
GlobalStateHandle::from_global(self),
false,
self.safe_option.clone(),
);
self.object_allocator.create_thread(&mut self.gc, thread)
}
pub fn resume_thread(
&mut self,
thread_val: LuaValue,
args: Vec<LuaValue>,
) -> LuaResult<(bool, Vec<LuaValue>)> {
let Some(l) = thread_val.as_thread_mut() else {
return Err(self.error("invalid thread".to_string()));
};
if l.is_main_thread() {
return Err(self.error("cannot resume main thread".to_string()));
}
l.resume(args)
}
#[inline(always)]
pub fn raw_get(&self, table_value: &LuaValue, key: &LuaValue) -> Option<LuaValue> {
let table = table_value.as_table()?;
table.raw_get(key)
}
pub fn table_pairs(&self, table_value: &LuaValue) -> LuaResult<Vec<(LuaValue, LuaValue)>> {
let table = table_value.as_table().ok_or(LuaError::RuntimeError)?;
Ok(table.iter_all())
}
pub fn table_length(&self, table_value: &LuaValue) -> LuaResult<usize> {
let table = table_value.as_table().ok_or(LuaError::RuntimeError)?;
Ok(table.len())
}
pub fn register_enum_of<T: LuaEnum>(&mut self, name: &str) -> LuaResult<()> {
let variants = T::variants();
let table = self.create_table(0, variants.len())?;
for &(vname, value) in variants {
let key = self.create_string(vname)?;
let val = LuaValue::integer(value);
self.raw_set(&table, key, val);
}
self.set_global(name, table)
}
#[inline(always)]
pub fn raw_set(&mut self, table_value: &LuaValue, key: LuaValue, value: LuaValue) -> bool {
let Some(table) = table_value.as_table_mut() else {
return false;
};
let (new_key, delta) = table.raw_set(&key, value);
if delta != 0
&& let Some(table_ptr) = table_value.as_table_ptr()
{
self.gc.track_resize(table_ptr, delta);
}
let need_barrier = (new_key && key.iscollectable()) || value.iscollectable();
if need_barrier && let Some(gc_ptr) = table_value.as_gc_ptr() {
self.gc.barrier_back(gc_ptr);
}
true
}
#[inline(always)]
pub fn raw_geti(&self, table_value: &LuaValue, key: i64) -> Option<LuaValue> {
let table = table_value.as_table()?;
table.raw_geti(key)
}
pub fn raw_seti(&mut self, table_value: &LuaValue, key: i64, value: LuaValue) -> bool {
let Some(table) = table_value.as_table_mut() else {
return false;
};
let delta = table.raw_seti(key, value);
if delta != 0
&& let Some(table_ptr) = table_value.as_table_ptr()
{
self.gc.track_resize(table_ptr, delta);
}
if value.is_collectable()
&& let Some(gc_ptr) = table_value.as_gc_ptr()
{
self.gc.barrier_back(gc_ptr);
}
true
}
#[inline]
pub fn create_string(&mut self, s: &str) -> CreateResult {
self.object_allocator.create_string(&mut self.gc, s)
}
#[inline]
pub fn create_binary(&mut self, data: Vec<u8>) -> CreateResult {
self.object_allocator.create_binary(&mut self.gc, data)
}
#[inline]
pub fn create_bytes(&mut self, bytes: &[u8]) -> CreateResult {
self.object_allocator.create_bytes(&mut self.gc, bytes)
}
#[inline]
pub fn create_string_owned(&mut self, s: String) -> CreateResult {
self.object_allocator.create_string_owned(&mut self.gc, s)
}
#[inline(always)]
pub fn create_table(&mut self, array_size: usize, hash_size: usize) -> CreateResult {
self.object_allocator
.create_table(&mut self.gc, array_size, hash_size)
}
pub fn create_userdata(&mut self, data: LuaUserdata) -> CreateResult {
self.object_allocator.create_userdata(&mut self.gc, data)
}
#[inline(always)]
pub fn create_proto(&mut self, chunk: LuaProto) -> LuaResult<ProtoPtr> {
self.object_allocator.create_proto(&mut self.gc, chunk)
}
#[inline(always)]
pub fn create_function(&mut self, chunk: ProtoPtr, upvalues: UpvalueStore) -> CreateResult {
self.object_allocator
.create_function(&mut self.gc, chunk, upvalues)
}
#[inline]
pub fn create_c_closure(&mut self, func: CFunction, upvalues: Vec<LuaValue>) -> CreateResult {
self.object_allocator
.create_c_closure(&mut self.gc, func, upvalues)
}
#[inline]
pub fn create_rclosure(&mut self, func: RustCallback, upvalues: Vec<LuaValue>) -> CreateResult {
self.object_allocator
.create_rclosure(&mut self.gc, func, upvalues)
}
#[inline]
pub fn create_closure<F>(&mut self, func: F) -> CreateResult
where
F: Fn(&mut LuaState) -> LuaResult<usize> + 'static,
{
self.create_rclosure(Box::new(func), Vec::new())
}
#[inline]
pub fn create_closure_with_upvalues<F>(
&mut self,
func: F,
upvalues: Vec<LuaValue>,
) -> CreateResult
where
F: Fn(&mut LuaState) -> LuaResult<usize> + 'static,
{
self.create_rclosure(Box::new(func), upvalues)
}
#[inline(always)]
pub fn create_upvalue_open(
&mut self,
stack_index: usize,
stk_id: StkId,
) -> LuaResult<UpvaluePtr> {
let upval = LuaUpvalue::new_open(stack_index, stk_id);
self.object_allocator.create_upvalue(&mut self.gc, upval)
}
#[inline(always)]
pub fn create_upvalue_closed(&mut self, value: LuaValue) -> LuaResult<UpvaluePtr> {
let upval = LuaUpvalue::new_closed(value);
self.object_allocator.create_upvalue(&mut self.gc, upval)
}
#[inline(always)]
fn check_gc(&mut self, l: &mut LuaState) -> bool {
if self.gc.gc_debt <= 0 {
self.gc.step(l);
return true;
}
false
}
fn full_gc(&mut self, l: &mut LuaState, is_emergency: bool) {
self.gc.gc_emergency = is_emergency;
match self.gc.gc_kind {
GcKind::GenMinor => {
self.full_gen(l);
}
GcKind::Inc => {
self.full_inc(l);
}
GcKind::GenMajor => {
self.gc.gc_kind = GcKind::Inc;
self.full_inc(l);
self.gc.gc_kind = GcKind::GenMajor;
}
}
self.object_allocator.trim_after_full_gc();
self.gc.gc_emergency = false;
}
fn full_inc(&mut self, l: &mut LuaState) {
if self.gc.keep_invariant() {
self.gc.enter_sweep(l);
}
self.gc.run_until_state(l, GcState::Pause);
self.gc.run_until_state(l, GcState::CallFin);
self.gc.run_until_state(l, GcState::Pause);
self.gc.set_pause();
}
fn full_gen(&mut self, l: &mut LuaState) {
self.gc.change_to_incremental_mode(l, GcKind::Inc);
self.gc.enter_gen(l);
}
pub fn gc_stats(&self) -> String {
let stats = self.gc.stats();
format!(
"GC Stats:\n\
- Bytes allocated: {}\n\
- Threshold: {}\n\
- Total collections: {}\n\
- Minor collections: {}\n\
- Major collections: {}\n\
- Objects collected: {}\n\
- Young generation size: {}\n\
- Old generation size: {}\n\
- Promoted objects: {}",
stats.bytes_allocated,
stats.threshold,
stats.collection_count,
stats.minor_collections,
stats.major_collections,
stats.objects_collected,
stats.young_gen_size,
stats.old_gen_size,
stats.promoted_objects
)
}
pub(crate) fn get_main_thread_ptr(&self) -> ThreadPtr {
self.main_state
}
pub fn get_basic_metatable(&self, kind: LuaValueKind) -> Option<LuaValue> {
match kind {
LuaValueKind::String => self.string_mt,
LuaValueKind::Integer | LuaValueKind::Float => self.number_mt,
LuaValueKind::Boolean => self.bool_mt,
LuaValueKind::Nil => self.nil_mt,
_ => None,
}
}
pub fn set_basic_metatable(&mut self, kind: LuaValueKind, mt: Option<LuaValue>) {
match kind {
LuaValueKind::String => self.string_mt = mt,
LuaValueKind::Integer | LuaValueKind::Float => self.number_mt = mt,
LuaValueKind::Boolean => self.bool_mt = mt,
LuaValueKind::Nil => self.nil_mt = mt,
_ => {}
}
}
pub fn get_basic_metatables(&self) -> Vec<LuaValue> {
let mut mts = Vec::new();
if let Some(mt) = &self.string_mt {
mts.push(*mt);
}
if let Some(mt) = &self.number_mt {
mts.push(*mt);
}
if let Some(mt) = &self.bool_mt {
mts.push(*mt);
}
if let Some(mt) = &self.nil_mt {
mts.push(*mt);
}
mts
}
}
#[derive(Clone, Copy)]
pub(crate) struct GlobalStateHandle(NonNull<GlobalState>);
impl GlobalStateHandle {
pub(crate) fn from_global(state: &mut GlobalState) -> Self {
Self(NonNull::from(state))
}
pub(crate) fn as_ref<'a>(self) -> &'a GlobalState {
unsafe { self.0.as_ref() }
}
pub(crate) fn as_mut<'a>(mut self) -> &'a mut GlobalState {
unsafe { self.0.as_mut() }
}
pub(crate) fn gc_debt(self) -> isize {
self.as_ref().gc.gc_debt
}
pub(crate) fn gc_barrier(
self,
state: *mut LuaState,
owner_ptr: GcObjectPtr,
value_gc_ptr: GcObjectPtr,
) {
self.as_mut()
.gc
.barrier(unsafe { &mut *state }, owner_ptr, value_gc_ptr);
}
pub(crate) fn check_gc(self, state: *mut LuaState) -> bool {
self.as_mut().check_gc(unsafe { &mut *state })
}
pub(crate) fn full_gc(self, state: *mut LuaState, emergency: bool) {
self.as_mut().full_gc(unsafe { &mut *state }, emergency);
}
pub(crate) fn change_gc_mode(self, state: *mut LuaState, kind: GcKind) {
self.as_mut().gc.change_mode(unsafe { &mut *state }, kind);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_count_hook_preserves_multret_unpack_results() {
let mut vm = GlobalState::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let results = vm
.main_state()
.execute(
r#"
local count = 0
local function f(...) return #({...}), ... end
local a = {}
for i = 1, 30 do a[i] = i end
debug.sethook(function() count = count + 1 end, '', 1)
local t = {f(table.unpack(a, 1, 30))}
debug.sethook()
return #t, t[#t], count > 0
"#,
)
.unwrap();
assert_eq!(results.len(), 3);
assert_eq!(results[0].as_integer(), Some(31));
assert_eq!(results[1].as_integer(), Some(30));
assert_eq!(results[2].as_bool(), Some(true));
}
#[cfg(feature = "serde")]
#[test]
fn test_json_serialization() {
let mut vm = GlobalState::new(SafeOption::default());
let num = LuaValue::number(42.5);
let json = vm.serialize_to_json(&num).unwrap();
assert_eq!(json, serde_json::json!(42.5));
let bool_val = LuaValue::boolean(true);
let json = vm.serialize_to_json(&bool_val).unwrap();
assert_eq!(json, serde_json::json!(true));
let nil = LuaValue::nil();
let json = vm.serialize_to_json(&nil).unwrap();
assert_eq!(json, serde_json::json!(null));
let str_val = vm.create_string("hello world").unwrap();
let json = vm.serialize_to_json(&str_val).unwrap();
assert_eq!(json, serde_json::json!("hello world"));
let arr = vm.create_table(3, 0).unwrap();
vm.raw_set(&arr, LuaValue::number(1.0), LuaValue::number(10.0));
vm.raw_set(&arr, LuaValue::number(2.0), LuaValue::number(20.0));
vm.raw_set(&arr, LuaValue::number(3.0), LuaValue::number(30.0));
let json = vm.serialize_to_json(&arr).unwrap();
assert_eq!(json, serde_json::json!([10, 20, 30]));
let obj = vm.create_table(0, 2).unwrap();
let key1 = vm.create_string("name").unwrap();
let key2 = vm.create_string("age").unwrap();
let val1 = vm.create_string("Alice").unwrap();
vm.raw_set(&obj, key1, val1);
vm.raw_set(&obj, key2, LuaValue::number(30.0));
let json = vm.serialize_to_json(&obj).unwrap();
let expected = serde_json::json!({"name": "Alice", "age": 30});
assert_eq!(json, expected);
let root = vm.create_table(0, 2).unwrap();
let inner = vm.create_table(2, 0).unwrap();
vm.raw_set(&inner, LuaValue::number(1.0), LuaValue::number(1.0));
vm.raw_set(&inner, LuaValue::number(2.0), LuaValue::number(2.0));
let key = vm.create_string("data").unwrap();
vm.raw_set(&root, key, inner);
let key2 = vm.create_string("count").unwrap();
vm.raw_set(&root, key2, LuaValue::number(100.0));
let json = vm.serialize_to_json(&root).unwrap();
let expected = serde_json::json!({"data": [1, 2], "count": 100});
assert_eq!(json, expected);
println!("✓ JSON serialization test passed");
}
#[cfg(feature = "serde")]
#[test]
fn test_json_deserialization() {
let mut vm = GlobalState::new(SafeOption::default());
let json = serde_json::json!(42);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert_eq!(lua_val.as_number(), Some(42.0));
let json = serde_json::json!(true);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert_eq!(lua_val.as_bool(), Some(true));
let json = serde_json::json!(null);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert!(lua_val.is_nil());
let json = serde_json::json!("hello");
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert_eq!(lua_val.as_str(), Some("hello"));
let json = serde_json::json!([1, 2, 3]);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert!(lua_val.is_table());
let val1 = vm.raw_get(&lua_val, &LuaValue::number(1.0)).unwrap();
assert_eq!(val1.as_number(), Some(1.0));
let json = serde_json::json!({"name": "Bob", "age": 25});
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert!(lua_val.is_table());
let key = vm.create_string("name").unwrap();
let name = vm.raw_get(&lua_val, &key).unwrap();
assert_eq!(name.as_str(), Some("Bob"));
println!("✓ JSON deserialization test passed");
}
#[cfg(feature = "serde")]
#[test]
fn test_json_roundtrip() {
let mut vm = GlobalState::new(SafeOption::default());
let root = vm.create_table(0, 3).unwrap();
let key1 = vm.create_string("name").unwrap();
let val1 = vm.create_string("Test").unwrap();
vm.raw_set(&root, key1, val1);
let key2 = vm.create_string("count").unwrap();
vm.raw_set(&root, key2, LuaValue::number(42.0));
let key3 = vm.create_string("items").unwrap();
let items = vm.create_table(3, 0).unwrap();
vm.raw_set(&items, LuaValue::number(1.0), LuaValue::number(10.0));
vm.raw_set(&items, LuaValue::number(2.0), LuaValue::number(20.0));
vm.raw_set(&items, LuaValue::number(3.0), LuaValue::number(30.0));
vm.raw_set(&root, key3, items);
let json = vm.serialize_to_json(&root).unwrap();
let reconstructed = vm.deserialize_from_json(&json).unwrap();
assert!(reconstructed.is_table());
let key = vm.create_string("name").unwrap();
let name = vm.raw_get(&reconstructed, &key).unwrap();
assert_eq!(name.as_str(), Some("Test"));
let key = vm.create_string("count").unwrap();
let count = vm.raw_get(&reconstructed, &key).unwrap();
assert_eq!(count.as_number(), Some(42.0));
println!("✓ JSON roundtrip test passed");
}
#[cfg(feature = "shared-proto")]
#[test]
fn test_shared_const_strings_reused_across_vms() {
let vm1 = GlobalState::new(SafeOption::default());
let vm2 = GlobalState::new(SafeOption::default());
let gc_tm1 = vm1.const_strings.tmname[TmKind::Gc as usize]
.as_string_ptr()
.unwrap();
let gc_tm2 = vm2.const_strings.tmname[TmKind::Gc as usize]
.as_string_ptr()
.unwrap();
let type_name1 = vm1.const_strings.str_number.as_string_ptr().unwrap();
let type_name2 = vm2.const_strings.str_number.as_string_ptr().unwrap();
assert_eq!(gc_tm1, gc_tm2);
assert_eq!(type_name1, type_name2);
assert!(gc_tm1.as_ref().header.is_shared());
assert!(type_name1.as_ref().header.is_shared());
}
}