Skip to main content

luaur_bytecode/methods/
bytecode_builder_dump_current_function.rs

1use crate::enums::dump_flags::DumpFlags;
2use crate::functions::get_base_type_string::get_base_type_string;
3use crate::records::bytecode_builder::BytecodeBuilder;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::ffi::CStr;
7use luaur_common::enums::luau_bytecode_type::{LuauBytecodeType, LBC_TYPE_OPTIONAL_BIT};
8use luaur_common::enums::luau_opcode::LuauOpcode;
9use luaur_common::functions::format_append::formatAppend;
10use luaur_common::functions::get_jump_target::get_jump_target;
11use luaur_common::functions::get_op_length::get_op_length;
12use luaur_common::macros::luau_assert::LUAU_ASSERT;
13use luaur_common::macros::luau_insn_op::LUAU_INSN_OP;
14
15impl BytecodeBuilder {
16    pub fn dump_current_function(&self, dumpinstoffs: &mut Vec<i32>) -> String {
17        if (self.dump_flags & (DumpFlags::Dump_Code as u32 | DumpFlags::Dump_Constants as u32)) == 0
18        {
19            return String::new();
20        }
21
22        let mut last_line = -1;
23        let mut next_remark = 0;
24        let mut result = String::new();
25
26        if self.dump_flags & DumpFlags::Dump_Locals as u32 != 0 {
27            for i in 0..self.debug_locals.len() {
28                let l = &self.debug_locals[i as usize];
29
30                if l.startpc == l.endpc {
31                    LUAU_ASSERT!(l.startpc < self.lines.len() as u32);
32
33                    formatAppend(
34                        &mut result,
35                        format_args!(
36                            "local {}: reg {}, start pc {} line {}, no live range\n",
37                            i, l.reg, l.startpc, self.lines[l.startpc as usize]
38                        ),
39                    );
40                } else {
41                    LUAU_ASSERT!(l.startpc < l.endpc);
42                    LUAU_ASSERT!(l.startpc < self.lines.len() as u32);
43                    LUAU_ASSERT!(l.endpc <= self.lines.len() as u32);
44
45                    formatAppend(
46                        &mut result,
47                        format_args!(
48                            "local {}: reg {}, start pc {} line {}, end pc {} line {}\n",
49                            i,
50                            l.reg,
51                            l.startpc,
52                            self.lines[l.startpc as usize],
53                            l.endpc - 1,
54                            self.lines[(l.endpc - 1) as usize]
55                        ),
56                    );
57                }
58            }
59        }
60
61        if self.dump_flags & DumpFlags::Dump_Types as u32 != 0 {
62            let typeinfo = &self.functions.last().unwrap().typeinfo;
63            let typeinfo_bytes = typeinfo.as_bytes();
64
65            for i in 2..typeinfo_bytes.len() {
66                let et = typeinfo_bytes[i];
67
68                // C++ `name = userdata ? userdata : getBaseTypeString(et)`.
69                let name = match self.try_get_userdata_type_name(LuauBytecodeType(et as u16)) {
70                    Some(s) => alloc::borrow::Cow::Borrowed(s),
71                    None => unsafe { CStr::from_ptr(get_base_type_string(et)).to_string_lossy() },
72                };
73                let optional = if (et as u16 & LBC_TYPE_OPTIONAL_BIT.0) != 0 {
74                    "?"
75                } else {
76                    ""
77                };
78
79                formatAppend(
80                    &mut result,
81                    format_args!("R{}: {}{} [argument]\n", i - 2, name, optional),
82                );
83            }
84
85            for i in 0..self.typed_upvals.len() {
86                let l = &self.typed_upvals[i];
87
88                let name = match self.try_get_userdata_type_name(l.r#type) {
89                    Some(s) => alloc::borrow::Cow::Borrowed(s),
90                    None => unsafe {
91                        CStr::from_ptr(get_base_type_string(l.r#type.0 as u8)).to_string_lossy()
92                    },
93                };
94                let optional = if (l.r#type.0 & LBC_TYPE_OPTIONAL_BIT.0) != 0 {
95                    "?"
96                } else {
97                    ""
98                };
99
100                formatAppend(&mut result, format_args!("U{}: {}{}\n", i, name, optional));
101            }
102
103            for i in 0..self.typed_locals.len() {
104                let l = &self.typed_locals[i];
105
106                let name = match self.try_get_userdata_type_name(l.r#type) {
107                    Some(s) => alloc::borrow::Cow::Borrowed(s),
108                    None => unsafe {
109                        CStr::from_ptr(get_base_type_string(l.r#type.0 as u8)).to_string_lossy()
110                    },
111                };
112                let optional = if (l.r#type.0 & LBC_TYPE_OPTIONAL_BIT.0) != 0 {
113                    "?"
114                } else {
115                    ""
116                };
117
118                formatAppend(
119                    &mut result,
120                    format_args!(
121                        "R{}: {}{} from {} to {}\n",
122                        l.reg, name, optional, l.startpc, l.endpc
123                    ),
124                );
125            }
126        }
127
128        if self.dump_flags & DumpFlags::Dump_Constants as u32 != 0 {
129            for i in 0..self.constants.len() {
130                formatAppend(&mut result, format_args!("K{}: ", i));
131                self.dump_constant(&mut result, i as i32, true);
132                formatAppend(&mut result, format_args!("\n"));
133            }
134        }
135
136        if self.dump_flags & DumpFlags::Dump_Code as u32 != 0 {
137            let mut labels = vec![-1; self.insns.len()];
138
139            let mut i = 0;
140            while i < self.insns.len() {
141                let target = get_jump_target(self.insns[i], i as u32);
142
143                if target >= 0 {
144                    LUAU_ASSERT!((target as usize) < self.insns.len());
145                    labels[target as usize] = 0;
146                }
147
148                let op: LuauOpcode =
149                    unsafe { core::mem::transmute(LUAU_INSN_OP(self.insns[i]) as u8) };
150                let op_len = get_op_length(op) as usize;
151                i += op_len;
152                LUAU_ASSERT!(i <= self.insns.len());
153            }
154
155            let mut next_label = 0;
156
157            for i in 0..labels.len() {
158                if labels[i] == 0 {
159                    labels[i] = next_label;
160                    next_label += 1;
161                }
162            }
163
164            dumpinstoffs.resize(self.insns.len() + 1, -1);
165
166            let mut i = 0;
167            while i < self.insns.len() {
168                let code = &self.insns[i];
169                let op = LUAU_INSN_OP(*code) as u8;
170
171                dumpinstoffs[i] = result.len() as i32;
172
173                // C++: `if (op == LOP_PREPVARARGS) { i++; continue; }` — the vararg
174                // prologue is a call-dispatch header with no "interesting" info and
175                // is never disassembled. (The prior `op == 32` was a mistranslated
176                // literal; LOP_PREPVARARGS is 65, so the skip never fired and the
177                // header reached `dump_instruction`'s unsupported-opcode assert.)
178                if op == LuauOpcode::LOP_PREPVARARGS as u8 {
179                    i += 1;
180                    continue;
181                }
182
183                if self.dump_flags & DumpFlags::Dump_Remarks as u32 != 0 {
184                    while next_remark < self.debug_remarks.len()
185                        && self.debug_remarks[next_remark].0 == i as u32
186                    {
187                        let remark_start = self.debug_remarks[next_remark].1 as usize;
188                        let remark_end = if next_remark + 1 < self.debug_remarks.len() {
189                            self.debug_remarks[next_remark + 1].1 as usize
190                        } else {
191                            self.debug_remark_buffer.len()
192                        };
193                        // C++ reads `debugRemarkBuffer.c_str() + offset` — a C-string that stops
194                        // at the null terminator. remark_end points at the *next* remark (past this
195                        // remark's '\0'), so slice only up to the terminator.
196                        let remark_str = self.debug_remark_buffer[remark_start..remark_end]
197                            .split('\0')
198                            .next()
199                            .unwrap_or("");
200                        formatAppend(&mut result, format_args!("REMARK {}\n", remark_str));
201                        next_remark += 1;
202                    }
203                }
204
205                if self.dump_flags & DumpFlags::Dump_Source as u32 != 0 {
206                    let line = self.lines[i];
207
208                    if line > 0 && line != last_line {
209                        LUAU_ASSERT!(((line - 1) as usize) < self.dump_source.len());
210                        formatAppend(
211                            &mut result,
212                            format_args!("{:5}: {}\n", line, self.dump_source[(line - 1) as usize]),
213                        );
214                        last_line = line;
215                    }
216                }
217
218                if self.dump_flags & DumpFlags::Dump_Lines as u32 != 0 {
219                    formatAppend(&mut result, format_args!("{}: ", self.lines[i]));
220                }
221
222                if labels[i] != -1 {
223                    formatAppend(&mut result, format_args!("L{}: ", labels[i]));
224                }
225
226                let target = get_jump_target(*code, i as u32);
227                let target_label = if target >= 0 {
228                    labels[target as usize]
229                } else {
230                    -1
231                };
232
233                // Pass the full remaining instruction stream (not just one word):
234                // multi-word ops (LOADKX, GETIMPORT, FASTCALL2K, NEWCLASSMEMBER,
235                // CMPPROTO, …) read their aux word via `code[1]`, which would be
236                // out of bounds on a length-1 slice. C++ passes a bare pointer.
237                self.dump_instruction(&self.insns[i..], &mut result, target_label);
238
239                let op: LuauOpcode = unsafe { core::mem::transmute(op) };
240                let op_len = get_op_length(op) as usize;
241                i += op_len;
242                LUAU_ASSERT!(i <= self.insns.len());
243            }
244
245            dumpinstoffs[self.insns.len()] = result.len() as i32;
246        }
247
248        result
249    }
250}