use luau_common::ByteSlice;
use crate::call::{CallRuntime, ThreadStack};
use crate::debug::DebugRuntime;
use crate::function::FunctionRuntime;
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::state::{
GlobalState, ProtectedErrorAction, THREAD_STATUS_BREAK, THREAD_STATUS_ERR_ERR,
THREAD_STATUS_ERR_MEM, THREAD_STATUS_ERR_RUN, THREAD_STATUS_ERR_SYNTAX, ThreadState,
};
use crate::string::StringRuntime;
use crate::thread::Thread;
use crate::value::{TValue, TValueCursor};
use crate::{VmControl, VmError, VmExit, VmResult};
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait ErrorRuntime: Sealed {
unsafe fn set_error_object(&self, error_code: i32, old_top: TValueCursor);
}
impl ErrorRuntime for Thread {
unsafe fn set_error_object(&self, error_code: i32, old_top: TValueCursor) {
unsafe {
match error_code {
x if x == THREAD_STATUS_ERR_MEM as i32 => {
let message = self
.intern_string(crate::state::LUA_MEMERRMSG.as_bstr())
.expect("memory error message is fixed during state initialization");
old_top.value_unchecked().set_string_value(message)
}
x if x == THREAD_STATUS_ERR_ERR as i32 => {
let message = self
.intern_string(crate::state::LUA_ERRERRMSG.as_bstr())
.expect("error handler message is fixed during state initialization");
old_top.value_unchecked().set_string_value(message)
}
x if x == THREAD_STATUS_ERR_SYNTAX as i32 || x == THREAD_STATUS_ERR_RUN as i32 => {
let top_value = self.stack_top().sub(1).value_unchecked();
old_top.value_unchecked().set_obj(top_value);
}
_ => unreachable!("invalid Luau error code {error_code}"),
}
self.set_stack_top(old_top.add(1));
}
}
}
#[repr(C)]
pub(crate) struct ErrorFunctionContext {
pub(crate) error_function: TValue,
}
pub(crate) unsafe fn call_error_function(
thread: &Thread,
context: &mut ErrorFunctionContext,
) -> VmResult {
unsafe {
let top = thread.stack_top();
let top_offset = thread.save_stack(top);
top.value_unchecked().set_obj(top.sub(1).value_unchecked());
top.sub(1).value_unchecked().set_obj(context.error_function);
if thread.check_stack(1) == 0 {
return crate::run_error!(thread, "stack limit").map_err(Into::into);
}
let top = thread.restore_stack(top_offset);
thread.set_stack_top(top.add(1));
thread.call_no_yield(top.sub(1), 1)?;
}
Ok(())
}
pub type Pfunc<T> = unsafe fn(&Thread, &mut T) -> VmResult;
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait ProtectedCall: Sealed {
unsafe fn raw_run_protected<T>(&self, function: Pfunc<T>, userdata: &mut T) -> VmResult;
unsafe fn protected_call_internal<T>(
&self,
function: Pfunc<T>,
userdata: &mut T,
old_top: isize,
error_function: isize,
) -> VmResult;
}
#[repr(C)]
pub struct LuaProtectedErrorFrame {
_private: [u8; 0],
}
#[repr(C)]
struct ActiveProtectedErrorFrame {
prev: *mut LuaProtectedErrorFrame,
}
struct ProtectedErrorGuard {
global: GlobalState,
prev: *mut LuaProtectedErrorFrame,
}
impl Drop for ProtectedErrorGuard {
fn drop(&mut self) {
self.global.set_protected_error(self.prev);
}
}
impl GlobalState {
pub fn protected_error(&self) -> *mut LuaProtectedErrorFrame {
unsafe { self.as_ptr().as_ref().unwrap_unchecked().protected_error }
}
pub fn set_protected_error(&self, protected_error: *mut LuaProtectedErrorFrame) {
unsafe {
self.as_ptr().as_mut().unwrap_unchecked().protected_error = protected_error;
}
}
}
impl ProtectedCall for Thread {
unsafe fn raw_run_protected<T>(&self, function: Pfunc<T>, userdata: &mut T) -> VmResult {
let global = unsafe { self.global() };
let mut frame = ActiveProtectedErrorFrame {
prev: global.protected_error(),
};
global.set_protected_error((&raw mut frame).cast());
let _protected_error_guard = ProtectedErrorGuard {
global,
prev: frame.prev,
};
unsafe { function(self, userdata) }
}
unsafe fn protected_call_internal<T>(
&self,
function: Pfunc<T>,
userdata: &mut T,
old_top: isize,
error_function: isize,
) -> VmResult {
unsafe {
let old_native_call_depth = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth;
let old_base_native_call_depth = self
.as_ptr()
.as_ref()
.unwrap_unchecked()
.base_native_call_depth;
let old_ci = self.save_ci(self.current_call_info_cursor());
let old_active = self.as_ptr().as_ref().unwrap_unchecked().is_active;
let mut result = self.raw_run_protected(function, userdata);
if let Err(exit) = result {
let VmExit::Error(mut error) = exit else {
return result;
};
let mut error_object = error;
if error_function != 0 {
if error != VmError::Runtime {
self.set_error_object(error.status(), self.stack_top());
}
let mut error_context = ErrorFunctionContext {
error_function: self.restore_stack(error_function).value_unchecked(),
};
let error_function_result =
self.raw_run_protected(call_error_function, &mut error_context);
error_object = match error_function_result {
Ok(()) => VmError::Runtime,
Err(VmExit::Error(VmError::Memory)) if error == VmError::Memory => {
VmError::Memory
}
Err(VmExit::Error(_)) => {
error = VmError::ErrorHandler;
VmError::ErrorHandler
}
Err(exit) => return Err(exit),
};
result = Err(VmExit::Error(error));
}
if !old_active {
self.as_ptr().as_mut().unwrap_unchecked().is_active = false;
}
let yieldable = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
<= self
.as_ptr()
.as_ref()
.unwrap_unchecked()
.base_native_call_depth;
self.as_ptr().as_mut().unwrap_unchecked().native_call_depth = old_native_call_depth;
self.as_ptr()
.as_mut()
.unwrap_unchecked()
.base_native_call_depth = old_base_native_call_depth;
if yieldable
&& let Some(callback) = self.global().protected_error_callback()
&& callback(self) == ProtectedErrorAction::Break
{
self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_BREAK;
return Err(VmExit::Control(VmControl::Break));
}
let restored_old_top = self.restore_stack(old_top);
let restored_ci = self.restore_ci(old_ci);
if self.open_upvalue().is_some() {
self.close(restored_old_top.value_unchecked());
}
self.set_error_object(error_object.status(), restored_old_top);
self.set_current_call_info(restored_ci);
self.set_stack_base(restored_ci.call_info_unchecked().base());
self.restore_stack_limit()?;
}
result
}
}
}