Skip to main content

luau_vm/
debug.rs

1use luau_bytecode::model::Instruction;
2use luau_bytecode::opcodes::Opcode;
3use luau_common::{BStr, ByteSlice};
4use luau_printf::Arg;
5
6use crate::function::Proto;
7use crate::handle::RawHandle;
8use crate::handle::sealed::Sealed;
9use crate::memory::MemoryRuntime;
10use crate::metamethod::{MetamethodRuntime, TmEvent};
11use crate::state::ThreadState;
12use crate::state::{CallInfo, ExecutionDisable};
13use crate::string::StringFormatting;
14use crate::string::TString;
15use crate::string::{LuaString, printf_error_message};
16use crate::thread::Thread;
17use crate::value::TValue;
18use crate::{VmError, VmErrorResult, VmResult};
19
20/// Unstable debug and runtime-error capability.
21///
22/// # Safety
23///
24/// Values, call frames, protos, and string handles must be live records from
25/// this thread's VM. Callers must maintain the current stack/call-frame shape
26/// and allow error operations to perform non-local VM control flow.
27#[allow(
28    clippy::missing_safety_doc,
29    reason = "all methods share the capability-level safety contract"
30)]
31pub trait DebugRuntime: Sealed {
32    /// `luaG_typeerror`
33    unsafe fn type_error<T>(&self, object: TValue, operation: &str) -> VmErrorResult<T>;
34
35    /// `luaG_forerror`
36    unsafe fn for_error<T>(&self, object: TValue, what: &str) -> VmErrorResult<T>;
37
38    /// `luaG_concaterror`
39    unsafe fn concat_error<T>(&self, left: TValue, right: TValue) -> VmErrorResult<T>;
40
41    /// `luaG_aritherror`
42    unsafe fn arith_error<T>(
43        &self,
44        left: TValue,
45        right: TValue,
46        operation: TmEvent,
47    ) -> VmErrorResult<T>;
48
49    /// `luaG_ordererror`
50    unsafe fn order_error<T>(
51        &self,
52        left: TValue,
53        right: TValue,
54        operation: TmEvent,
55    ) -> VmErrorResult<T>;
56
57    /// `luaG_indexerror`
58    unsafe fn index_error<T>(&self, object: TValue, key: TValue) -> VmErrorResult<T>;
59
60    /// `luaG_methoderror`
61    unsafe fn method_error<T>(&self, object: TValue, key: TString) -> VmErrorResult<T>;
62
63    /// `luaG_missingmembererror`
64    unsafe fn missing_member_error<T>(&self, object: TValue, key: TValue) -> VmErrorResult<T>;
65
66    /// `luaG_readonlyerror`
67    unsafe fn readonly_error<T>(&self) -> VmErrorResult<T>;
68
69    /// `luaG_runerror`
70    unsafe fn run_error<'a, T, F, A>(&self, format: F, args: A) -> VmErrorResult<T>
71    where
72        F: AsRef<[u8]>,
73        A: AsMut<[luau_printf::Arg<'a>]>;
74
75    /// `luaG_pusherror`
76    unsafe fn push_error(&self, error: &BStr) -> VmErrorResult;
77
78    /// `luaG_breakpoint`
79    unsafe fn breakpoint_internal(&self, proto: Proto, line: i32, enable: bool) -> VmErrorResult;
80
81    /// `luaG_onbreak`
82    unsafe fn on_break(&self) -> bool;
83
84    /// `luaG_isnative`
85    unsafe fn is_native(&self, level: i32) -> i32;
86
87    /// `luaG_hasnative`
88    unsafe fn has_native(&self, level: i32) -> i32;
89}
90
91/// `LUA_IDSIZE`
92pub const LUA_ID_SIZE: usize = 256;
93
94pub struct LuaDebug {
95    pub name: Option<LuaString>,
96    pub what: LuaString,
97    pub source: LuaString,
98    pub short_src: [u8; LUA_ID_SIZE],
99    pub linedefined: i32,
100    pub currentline: i32,
101    pub nupvals: u8,
102    pub nparams: u8,
103    pub is_vararg: bool,
104    pub userdata: *mut (),
105}
106
107impl Default for LuaDebug {
108    fn default() -> Self {
109        Self {
110            name: None,
111            what: LuaString::from_static(b"".as_bstr()),
112            source: LuaString::from_static(b"".as_bstr()),
113            short_src: [0; LUA_ID_SIZE],
114            linedefined: 0,
115            currentline: 0,
116            nupvals: 0,
117            nparams: 0,
118            is_vararg: false,
119            userdata: core::ptr::null_mut(),
120        }
121    }
122}
123
124impl LuaDebug {
125    pub fn short_src(&self) -> &[u8] {
126        let len = self
127            .short_src
128            .iter()
129            .position(|byte| *byte == 0)
130            .unwrap_or(self.short_src.len());
131        &self.short_src[..len]
132    }
133
134    pub(crate) fn set_short_src(&mut self, source: &[u8]) {
135        self.short_src.fill(0);
136        let len = source.len().min(self.short_src.len());
137        self.short_src[..len].copy_from_slice(&source[..len]);
138    }
139}
140
141pub type LuaHook = fn(&Thread, &mut LuaDebug) -> VmResult;
142
143pub type LuaDebugInterruptHook = fn(&Thread, &mut LuaDebug) -> VmErrorResult;
144
145pub type LuaCoverage = fn(*mut (), Option<&BStr>, i32, i32, &[i32]);
146
147pub type LuaCounterFunction = fn(*mut (), Option<&BStr>, i32);
148
149pub type LuaCounterValue = fn(*mut (), i32, i32, u64);
150
151/// `luaO_chunkid`
152pub(crate) fn chunk_id<'a>(buffer: &'a mut [u8], source: &'a [u8]) -> &'a [u8] {
153    if source.first() == Some(&b'=') {
154        if source.len() <= buffer.len() {
155            return &source[1..];
156        }
157
158        let copy_len = buffer.len().saturating_sub(1);
159        buffer[..copy_len].copy_from_slice(&source[1..1 + copy_len]);
160        return &buffer[..copy_len];
161    }
162
163    if source.first() == Some(&b'@') {
164        if source.len() <= buffer.len() {
165            return &source[1..];
166        }
167
168        buffer[..3].copy_from_slice(b"...");
169        let tail_len = buffer.len() - 4;
170        let start = source.len() - tail_len;
171        buffer[3..3 + tail_len].copy_from_slice(&source[start..start + tail_len]);
172        return &buffer[..buffer.len() - 1];
173    }
174
175    let len = source
176        .iter()
177        .position(|byte| *byte == b'\n' || *byte == b'\r')
178        .unwrap_or(source.len());
179    let source = &source[..len];
180    let mut room = buffer.len() - b"[string \"...\"]".len() - 1;
181    if len < room {
182        room = len;
183    }
184
185    let mut write = 0;
186    buffer[write..write + 9].copy_from_slice(b"[string \"");
187    write += 9;
188
189    buffer[write..write + room].copy_from_slice(&source[..room]);
190    write += room;
191
192    if room < source.len() {
193        buffer[write..write + 3].copy_from_slice(b"...");
194        write += 3;
195    }
196
197    buffer[write..write + 2].copy_from_slice(b"\"]");
198    write += 2;
199    &buffer[..write]
200}
201
202/// `currentpc`
203pub(crate) fn current_pc(call_info: CallInfo, proto: Proto) -> i32 {
204    unsafe { proto.pc_rel(call_info.saved_pc()) }
205}
206
207/// `getluaproto`
208pub(crate) fn lua_proto(call_info: CallInfo) -> Option<Proto> {
209    if unsafe { call_info.is_lua() } {
210        unsafe { call_info.function_closure().proto() }
211    } else {
212        None
213    }
214}
215
216/// `luaG_breakpoint`
217fn patch_breakpoint(
218    thread: &Thread,
219    proto: Proto,
220    line: i32,
221    enable: bool,
222    disable: Option<ExecutionDisable>,
223) -> VmErrorResult {
224    let proto_ref = unsafe { proto.as_ptr().as_ref().unwrap_unchecked() };
225    if proto_ref.line_info.is_null() || (disable.is_none() && !proto_ref.exec_data.is_null()) {
226        for index in 0..proto_ref.size_p as usize {
227            let child = unsafe { proto.child_proto(index).unwrap_unchecked() };
228            patch_breakpoint(thread, child, line, enable, disable)?;
229        }
230
231        return Ok(());
232    }
233
234    for index in 0..proto_ref.size_code as usize {
235        let instruction = Instruction::new(unsafe { *proto_ref.code.add(index) });
236        if unsafe { instruction.opcode_unchecked() } == Opcode::PrepVarargs {
237            continue;
238        }
239
240        if unsafe { proto.get_line(index as i32) } != line {
241            continue;
242        }
243
244        if proto_ref.debug_insn.is_null() {
245            let size = proto_ref.size_code as usize;
246            let debug_insn = unsafe { thread.new_array::<u8>(size, proto_ref.memcat)? };
247
248            for debug_index in 0..size {
249                unsafe {
250                    *debug_insn.add(debug_index) =
251                        Instruction::new(*proto_ref.code.add(debug_index)).opcode_unchecked() as u8;
252                }
253            }
254
255            unsafe {
256                proto.as_ptr().as_mut().unwrap_unchecked().debug_insn = debug_insn;
257            }
258        }
259
260        let opcode = if enable {
261            Opcode::Break
262        } else {
263            let original = unsafe { *proto_ref.debug_insn.add(index) };
264            Opcode::from_byte(original)
265                .expect("breakpoint debug opcode must decode to a valid Luau opcode")
266        };
267
268        unsafe {
269            let code = proto_ref.code.add(index);
270            *code = (Instruction::new(*code).word() & !0xff) | u32::from(opcode as u8);
271        }
272
273        if enable
274            && !proto_ref.exec_data.is_null()
275            && let Some(disable) = disable
276        {
277            unsafe { disable(thread, proto) };
278        }
279
280        break;
281    }
282
283    for index in 0..proto_ref.size_p as usize {
284        let child = unsafe { proto.child_proto(index).unwrap_unchecked() };
285        patch_breakpoint(thread, child, line, enable, disable)?;
286    }
287    Ok(())
288}
289
290impl DebugRuntime for Thread {
291    /// `luaG_typeerror`
292    unsafe fn type_error<T>(&self, object: TValue, operation: &str) -> VmErrorResult<T> {
293        unsafe {
294            let type_name = self.obj_type_name(object);
295            crate::run_error!(self, "attempt to %s a %s value", operation, &type_name)
296        }
297    }
298
299    /// `luaG_forerror`
300    unsafe fn for_error<T>(&self, object: TValue, what: &str) -> VmErrorResult<T> {
301        unsafe {
302            let type_name = self.obj_type_name(object);
303            crate::run_error!(
304                self,
305                "invalid 'for' %s (number expected, got %s)",
306                what,
307                &type_name,
308            )
309        }
310    }
311
312    /// `luaG_concaterror`
313    unsafe fn concat_error<T>(&self, left: TValue, right: TValue) -> VmErrorResult<T> {
314        unsafe {
315            let left_type = self.obj_type_name(left);
316            let right_type = self.obj_type_name(right);
317            crate::run_error!(
318                self,
319                "attempt to concatenate %s with %s",
320                &left_type,
321                &right_type,
322            )
323        }
324    }
325
326    /// `luaG_aritherror`
327    unsafe fn arith_error<T>(
328        &self,
329        left: TValue,
330        right: TValue,
331        operation: TmEvent,
332    ) -> VmErrorResult<T> {
333        unsafe {
334            let left_type = self.obj_type_name(left);
335            let right_type = self.obj_type_name(right);
336            let operation_name = self.global().tm_name(operation as usize);
337            let op_name = &operation_name.as_bytes()[2..];
338            let left_bytes = left_type.as_bytes();
339            let right_bytes = right_type.as_bytes();
340
341            if left_bytes == right_bytes {
342                crate::run_error!(
343                    self,
344                    "attempt to perform arithmetic (%s) on %s",
345                    op_name,
346                    left_bytes,
347                )
348            } else {
349                crate::run_error!(
350                    self,
351                    "attempt to perform arithmetic (%s) on %s and %s",
352                    op_name,
353                    left_bytes,
354                    right_bytes,
355                )
356            }
357        }
358    }
359
360    /// `luaG_ordererror`
361    unsafe fn order_error<T>(
362        &self,
363        left: TValue,
364        right: TValue,
365        operation: TmEvent,
366    ) -> VmErrorResult<T> {
367        unsafe {
368            let left_type = self.obj_type_name(left);
369            let right_type = self.obj_type_name(right);
370            let op_name = match operation {
371                TmEvent::Lt => b"<".as_bstr(),
372                TmEvent::Le => b"<=".as_bstr(),
373                _ => b"==".as_bstr(),
374            };
375            crate::run_error!(
376                self,
377                "attempt to compare %s %s %s",
378                &left_type,
379                op_name,
380                &right_type,
381            )
382        }
383    }
384
385    /// `luaG_indexerror`
386    unsafe fn index_error<T>(&self, object: TValue, key: TValue) -> VmErrorResult<T> {
387        unsafe {
388            let object_type = self.obj_type_name(object);
389            let key_type = self.obj_type_name(key);
390            let object_bytes = object_type.as_bytes();
391            let key_type_bytes = key_type.as_bytes();
392
393            if key.is_string() {
394                let string = key.string_value();
395                let key_bytes = string.as_bytes();
396                if string.as_ptr().as_ref().unwrap_unchecked().len <= 64 {
397                    return crate::run_error!(
398                        self,
399                        "attempt to index %s with '%s'",
400                        object_bytes,
401                        key_bytes,
402                    );
403                }
404            }
405
406            crate::run_error!(
407                self,
408                "attempt to index %s with %s",
409                object_bytes,
410                key_type_bytes,
411            )
412        }
413    }
414
415    /// `luaG_methoderror`
416    unsafe fn method_error<T>(&self, object: TValue, key: TString) -> VmErrorResult<T> {
417        unsafe {
418            let object_type = self.obj_type_name(object);
419            crate::run_error!(
420                self,
421                "attempt to call missing method '%s' of %s",
422                key.as_bytes(),
423                &object_type,
424            )
425        }
426    }
427
428    /// `luaG_missingmembererror`
429    unsafe fn missing_member_error<T>(&self, object: TValue, key: TValue) -> VmErrorResult<T> {
430        unsafe {
431            let object_type = self.obj_type_name(object);
432            let object_bytes = object_type.as_bytes();
433
434            if !key.is_string() {
435                let key_type = self.obj_type_name(key);
436                crate::run_error!(self, "cannot index %s with a %s", object_bytes, &key_type)
437            } else {
438                let key_string = key.string_value();
439                crate::run_error!(
440                    self,
441                    "this %s does not have a key named '%s'",
442                    object_bytes,
443                    key_string.as_bytes(),
444                )
445            }
446        }
447    }
448
449    /// `luaG_readonlyerror`
450    unsafe fn readonly_error<T>(&self) -> VmErrorResult<T> {
451        unsafe { crate::run_error!(self, "attempt to modify a readonly table") }
452    }
453
454    /// `luaG_runerror`
455    unsafe fn run_error<'a, T, F, A>(&self, format: F, mut args: A) -> VmErrorResult<T>
456    where
457        F: AsRef<[u8]>,
458        A: AsMut<[Arg<'a>]>,
459    {
460        let format = format.as_ref();
461        let args = args.as_mut();
462        let mut formatted = Vec::new();
463        let message = if args.is_empty() {
464            format
465        } else {
466            formatted.reserve(format.len());
467            if let Err(error) =
468                luau_printf::printf_c_locale(&mut formatted, luau_printf::BStr::new(format), args)
469            {
470                unsafe { self.push_error(printf_error_message(&error).as_bstr())? };
471                return Err(VmError::Runtime);
472            }
473            formatted.as_slice()
474        };
475        let message_len = message
476            .iter()
477            .position(|byte| *byte == 0)
478            .unwrap_or(message.len())
479            .min(crate::thread::LUA_BUFFER_SIZE - 1);
480        unsafe { self.push_error(message[..message_len].as_bstr()) }?;
481        Err(VmError::Runtime)
482    }
483
484    /// `luaG_pusherror`
485    unsafe fn push_error(&self, error: &BStr) -> VmErrorResult {
486        unsafe {
487            self.raw_check_stack(1)?;
488
489            let call_info = self.current_call_info();
490            if call_info.is_lua() {
491                let proto = lua_proto(call_info).unwrap_unchecked();
492                let source = proto.source().unwrap_unchecked();
493                let mut chunk_buffer = [0u8; LUA_ID_SIZE];
494                let chunk_id = chunk_id(&mut chunk_buffer, source.as_bytes());
495                let line = proto.get_line(current_pc(call_info, proto));
496                let args = [
497                    Arg::string(chunk_id.as_bstr()),
498                    Arg::int(line),
499                    Arg::string(error),
500                ];
501                self.push_fstring_internal("%s:%d: %s", args)?;
502            } else {
503                self.push_string(error)?;
504            }
505        }
506        Ok(())
507    }
508
509    /// `luaG_breakpoint`
510    unsafe fn breakpoint_internal(&self, proto: Proto, line: i32, enable: bool) -> VmErrorResult {
511        let disable = unsafe { self.global() }.execution_disable();
512        patch_breakpoint(self, proto, line, enable, disable)
513    }
514
515    /// `luaG_onbreak`
516    unsafe fn on_break(&self) -> bool {
517        unsafe {
518            let call_info = self.current_call_info();
519            if call_info == self.base_call_info() {
520                return false;
521            }
522
523            if !call_info.is_lua() {
524                return false;
525            }
526
527            Instruction::new(call_info.saved_pc().read()).opcode_unchecked() == Opcode::Break
528        }
529    }
530
531    /// `luaG_isnative`
532    unsafe fn is_native(&self, level: i32) -> i32 {
533        let depth = unsafe { self.stack_depth() };
534        if level < 0 || level >= depth {
535            return 0;
536        }
537
538        let call_info = unsafe {
539            self.current_call_info_cursor()
540                .sub(level as usize)
541                .call_info_unchecked()
542        };
543        i32::from(
544            unsafe { call_info.as_ptr().as_ref().unwrap_unchecked().flags }
545                & crate::state::LUA_CALLINFO_NATIVE
546                != 0,
547        )
548    }
549
550    /// `luaG_hasnative`
551    unsafe fn has_native(&self, level: i32) -> i32 {
552        unsafe {
553            let depth = self.stack_depth();
554            if level < 0 || level >= depth {
555                return 0;
556            }
557
558            let call_info = self
559                .current_call_info_cursor()
560                .sub(level as usize)
561                .call_info_unchecked();
562            let Some(proto) = lua_proto(call_info) else {
563                return 0;
564            };
565
566            i32::from(
567                !proto
568                    .as_ptr()
569                    .as_ref()
570                    .unwrap_unchecked()
571                    .exec_data
572                    .is_null(),
573            )
574        }
575    }
576}