luau_vm/call/
protected.rs1use luau_common::ByteSlice;
2
3use crate::call::{CallRuntime, ThreadStack};
4use crate::debug::DebugRuntime;
5use crate::function::FunctionRuntime;
6use crate::handle::RawHandle;
7use crate::handle::sealed::Sealed;
8use crate::state::{
9 GlobalState, ProtectedErrorAction, THREAD_STATUS_BREAK, THREAD_STATUS_ERR_ERR,
10 THREAD_STATUS_ERR_MEM, THREAD_STATUS_ERR_RUN, THREAD_STATUS_ERR_SYNTAX, ThreadState,
11};
12use crate::string::StringRuntime;
13use crate::thread::Thread;
14use crate::value::{TValue, TValueCursor};
15use crate::{VmControl, VmError, VmExit, VmResult};
16
17#[allow(
24 clippy::missing_safety_doc,
25 reason = "all methods share the capability-level safety contract"
26)]
27pub trait ErrorRuntime: Sealed {
28 unsafe fn set_error_object(&self, error_code: i32, old_top: TValueCursor);
29}
30
31impl ErrorRuntime for Thread {
32 unsafe fn set_error_object(&self, error_code: i32, old_top: TValueCursor) {
34 unsafe {
35 match error_code {
36 x if x == THREAD_STATUS_ERR_MEM as i32 => {
37 let message = self
38 .intern_string(crate::state::LUA_MEMERRMSG.as_bstr())
39 .expect("memory error message is fixed during state initialization");
40 old_top.value_unchecked().set_string_value(message)
41 }
42 x if x == THREAD_STATUS_ERR_ERR as i32 => {
43 let message = self
44 .intern_string(crate::state::LUA_ERRERRMSG.as_bstr())
45 .expect("error handler message is fixed during state initialization");
46 old_top.value_unchecked().set_string_value(message)
47 }
48 x if x == THREAD_STATUS_ERR_SYNTAX as i32 || x == THREAD_STATUS_ERR_RUN as i32 => {
49 let top_value = self.stack_top().sub(1).value_unchecked();
50 old_top.value_unchecked().set_obj(top_value);
51 }
52 _ => unreachable!("invalid Luau error code {error_code}"),
53 }
54
55 self.set_stack_top(old_top.add(1));
56 }
57 }
58}
59
60#[repr(C)]
61pub(crate) struct ErrorFunctionContext {
62 pub(crate) error_function: TValue,
63}
64
65pub(crate) unsafe fn call_error_function(
67 thread: &Thread,
68 context: &mut ErrorFunctionContext,
69) -> VmResult {
70 unsafe {
71 let top = thread.stack_top();
72 let top_offset = thread.save_stack(top);
73
74 top.value_unchecked().set_obj(top.sub(1).value_unchecked());
75 top.sub(1).value_unchecked().set_obj(context.error_function);
76
77 if thread.check_stack(1) == 0 {
78 return crate::run_error!(thread, "stack limit").map_err(Into::into);
79 }
80
81 let top = thread.restore_stack(top_offset);
82 thread.set_stack_top(top.add(1));
83 thread.call_no_yield(top.sub(1), 1)?;
84 }
85 Ok(())
86}
87
88pub type Pfunc<T> = unsafe fn(&Thread, &mut T) -> VmResult;
89
90#[allow(
98 clippy::missing_safety_doc,
99 reason = "all methods share the capability-level safety contract"
100)]
101pub trait ProtectedCall: Sealed {
102 unsafe fn raw_run_protected<T>(&self, function: Pfunc<T>, userdata: &mut T) -> VmResult;
103 unsafe fn protected_call_internal<T>(
104 &self,
105 function: Pfunc<T>,
106 userdata: &mut T,
107 old_top: isize,
108 error_function: isize,
109 ) -> VmResult;
110}
111
112#[repr(C)]
113pub struct LuaProtectedErrorFrame {
114 _private: [u8; 0],
115}
116
117#[repr(C)]
118struct ActiveProtectedErrorFrame {
119 prev: *mut LuaProtectedErrorFrame,
120}
121
122struct ProtectedErrorGuard {
123 global: GlobalState,
124 prev: *mut LuaProtectedErrorFrame,
125}
126
127impl Drop for ProtectedErrorGuard {
128 fn drop(&mut self) {
129 self.global.set_protected_error(self.prev);
130 }
131}
132
133impl GlobalState {
134 pub fn protected_error(&self) -> *mut LuaProtectedErrorFrame {
135 unsafe { self.as_ptr().as_ref().unwrap_unchecked().protected_error }
136 }
137
138 pub fn set_protected_error(&self, protected_error: *mut LuaProtectedErrorFrame) {
139 unsafe {
140 self.as_ptr().as_mut().unwrap_unchecked().protected_error = protected_error;
141 }
142 }
143}
144
145impl ProtectedCall for Thread {
146 unsafe fn raw_run_protected<T>(&self, function: Pfunc<T>, userdata: &mut T) -> VmResult {
148 let global = unsafe { self.global() };
149 let mut frame = ActiveProtectedErrorFrame {
150 prev: global.protected_error(),
151 };
152 global.set_protected_error((&raw mut frame).cast());
153 let _protected_error_guard = ProtectedErrorGuard {
154 global,
155 prev: frame.prev,
156 };
157
158 unsafe { function(self, userdata) }
159 }
160
161 unsafe fn protected_call_internal<T>(
163 &self,
164 function: Pfunc<T>,
165 userdata: &mut T,
166 old_top: isize,
167 error_function: isize,
168 ) -> VmResult {
169 unsafe {
170 let old_native_call_depth = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth;
171 let old_base_native_call_depth = self
172 .as_ptr()
173 .as_ref()
174 .unwrap_unchecked()
175 .base_native_call_depth;
176 let old_ci = self.save_ci(self.current_call_info_cursor());
177 let old_active = self.as_ptr().as_ref().unwrap_unchecked().is_active;
178 let mut result = self.raw_run_protected(function, userdata);
179
180 if let Err(exit) = result {
181 let VmExit::Error(mut error) = exit else {
182 return result;
183 };
184 let mut error_object = error;
185
186 if error_function != 0 {
187 if error != VmError::Runtime {
188 self.set_error_object(error.status(), self.stack_top());
189 }
190
191 let mut error_context = ErrorFunctionContext {
192 error_function: self.restore_stack(error_function).value_unchecked(),
193 };
194 let error_function_result =
195 self.raw_run_protected(call_error_function, &mut error_context);
196
197 error_object = match error_function_result {
198 Ok(()) => VmError::Runtime,
199 Err(VmExit::Error(VmError::Memory)) if error == VmError::Memory => {
200 VmError::Memory
201 }
202 Err(VmExit::Error(_)) => {
203 error = VmError::ErrorHandler;
204 VmError::ErrorHandler
205 }
206 Err(exit) => return Err(exit),
207 };
208
209 result = Err(VmExit::Error(error));
210 }
211
212 if !old_active {
213 self.as_ptr().as_mut().unwrap_unchecked().is_active = false;
214 }
215
216 let yieldable = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
217 <= self
218 .as_ptr()
219 .as_ref()
220 .unwrap_unchecked()
221 .base_native_call_depth;
222 self.as_ptr().as_mut().unwrap_unchecked().native_call_depth = old_native_call_depth;
223 self.as_ptr()
224 .as_mut()
225 .unwrap_unchecked()
226 .base_native_call_depth = old_base_native_call_depth;
227
228 if yieldable
229 && let Some(callback) = self.global().protected_error_callback()
230 && callback(self) == ProtectedErrorAction::Break
231 {
232 self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_BREAK;
233 return Err(VmExit::Control(VmControl::Break));
234 }
235
236 let restored_old_top = self.restore_stack(old_top);
237 let restored_ci = self.restore_ci(old_ci);
238 if self.open_upvalue().is_some() {
239 self.close(restored_old_top.value_unchecked());
240 }
241
242 self.set_error_object(error_object.status(), restored_old_top);
243 self.set_current_call_info(restored_ci);
244 self.set_stack_base(restored_ci.call_info_unchecked().base());
245 self.restore_stack_limit()?;
246 }
247
248 result
249 }
250 }
251}