Skip to main content

luau_vm/
load.rs

1use core::mem::{MaybeUninit, size_of};
2use core::ptr;
3
4use luau_bytecode::model::Instruction;
5use luau_bytecode::opcodes::{
6    BYTECODE_TYPE_VERSION_MAX, BYTECODE_TYPE_VERSION_MIN, BYTECODE_VERSION_CLASSES,
7    BYTECODE_VERSION_MAX, BYTECODE_VERSION_MIN, BytecodeConstantTag, BytecodeTypeTag,
8    FEEDBACK_TYPE_CALLTARGET, Opcode, PROTO_FLAG_INLINABLE,
9};
10use luau_common::{ByteSlice, flags};
11
12use crate::Table;
13use crate::call::{ProtectedCall, ThreadStack};
14use crate::class::ClassRuntime;
15use crate::debug::LUA_ID_SIZE;
16use crate::debug::chunk_id;
17use crate::function::FunctionRuntime;
18use crate::function::Proto;
19use crate::function::{
20    FeedbackVectorSlot, FeedbackVectorSlotCallTarget, FeedbackVectorSlotData, RawLocVar, RawProto,
21};
22use crate::gc::GcObject;
23use crate::gc::{GcBarrier, GcRuntime};
24use crate::handle::RawHandle;
25use crate::memory::MemoryRuntime;
26use crate::state::ThreadState;
27use crate::state::{GlobalState, LUA_MEMERRMSG};
28use crate::string::RawTString;
29use crate::string::StringRuntime;
30use crate::string::TString;
31use crate::table::TableRuntime;
32use crate::thread::Thread;
33use crate::thread::stack::RawStackAccess;
34use crate::value::{RawTValue, TValueCursor};
35use crate::vm::VmOperations;
36use crate::{VmError, VmExit, VmResult};
37
38const USERDATA_TYPE_LIMIT: usize =
39    BytecodeTypeTag::TaggedUserdataEnd as usize - BytecodeTypeTag::TaggedUserdataBase as usize;
40
41struct TempBuffer<T> {
42    thread: *const Thread,
43    data: *mut T,
44    count: usize,
45}
46
47impl<T> TempBuffer<T> {
48    /// `TempBuffer::TempBuffer`
49    const fn new() -> Self {
50        Self {
51            thread: ptr::null(),
52            data: ptr::null_mut(),
53            count: 0,
54        }
55    }
56
57    /// `TempBuffer::allocate`
58    unsafe fn allocate(&mut self, thread: &Thread, count: usize) -> VmResult {
59        debug_assert!(self.thread.is_null());
60        self.thread = ptr::from_ref(thread);
61        self.count = count;
62
63        if count == 0 {
64            self.data = ptr::null_mut();
65            return Ok(());
66        }
67
68        self.data = unsafe { thread.new_array::<T>(count, 0)? };
69        Ok(())
70    }
71}
72
73impl<T> Drop for TempBuffer<T> {
74    /// `TempBuffer::~TempBuffer`
75    fn drop(&mut self) {
76        if !self.thread.is_null() && !self.data.is_null() {
77            unsafe { (&*self.thread).free_array(self.data, self.count, 0) };
78        }
79    }
80}
81
82struct ScopedGcThreshold {
83    global: GlobalState,
84    original_threshold: usize,
85}
86
87impl ScopedGcThreshold {
88    /// `ScopedSetGCThreshold::ScopedSetGCThreshold`
89    unsafe fn new(global: GlobalState, threshold: usize) -> Self {
90        let original_threshold =
91            unsafe { global.as_ptr().as_ref().unwrap_unchecked().gc_threshold };
92        unsafe { global.as_ptr().as_mut().unwrap_unchecked().gc_threshold = threshold };
93
94        Self {
95            global,
96            original_threshold,
97        }
98    }
99}
100
101impl Drop for ScopedGcThreshold {
102    /// `ScopedSetGCThreshold::~ScopedSetGCThreshold`
103    fn drop(&mut self) {
104        unsafe {
105            self.global
106                .as_ptr()
107                .as_mut()
108                .unwrap_unchecked()
109                .gc_threshold = self.original_threshold
110        };
111    }
112}
113
114struct LoadContext<'a> {
115    strings: TempBuffer<TString>,
116    protos: TempBuffer<Proto>,
117    chunk_name: &'a [u8],
118    data: &'a [u8],
119    env: i32,
120}
121
122#[repr(C)]
123struct ResolveImportContext {
124    constants: TValueCursor,
125    environment: Table,
126    id: u32,
127}
128
129/// `read`
130fn read<T: Copy>(data: &[u8], offset: &mut usize) -> Option<T> {
131    let end = offset.checked_add(size_of::<T>())?;
132    let bytes = data.get(*offset..end)?;
133
134    let mut value = MaybeUninit::<T>::uninit();
135    unsafe {
136        ptr::copy_nonoverlapping(
137            bytes.as_ptr(),
138            value.as_mut_ptr().cast::<u8>(),
139            size_of::<T>(),
140        );
141        *offset = end;
142        Some(value.assume_init())
143    }
144}
145
146fn read_slice<'a>(data: &'a [u8], offset: &mut usize, len: usize) -> Option<&'a [u8]> {
147    let end = offset.checked_add(len)?;
148    let bytes = data.get(*offset..end)?;
149    *offset = end;
150    Some(bytes)
151}
152
153fn read_slice_mut<'a>(data: &'a mut [u8], offset: &mut usize, len: usize) -> Option<&'a mut [u8]> {
154    let end = offset.checked_add(len)?;
155    let bytes = data.get_mut(*offset..end)?;
156    *offset = end;
157    Some(bytes)
158}
159
160/// `readVarInt`
161fn read_var_int(data: &[u8], offset: &mut usize) -> Option<u32> {
162    let mut result = 0u32;
163    let mut shift = 0u32;
164
165    loop {
166        if shift >= u32::BITS {
167            return None;
168        }
169
170        let byte = read::<u8>(data, offset)?;
171        result |= u32::from(byte & 127).checked_shl(shift)?;
172        shift += 7;
173
174        if byte & 128 == 0 {
175            return Some(result);
176        }
177    }
178}
179
180/// `readVarInt64`
181fn read_var_int64(data: &[u8], offset: &mut usize) -> Option<u64> {
182    let mut result = 0u64;
183    let mut shift = 0u32;
184
185    loop {
186        if shift >= u64::BITS {
187            return None;
188        }
189
190        let byte = read::<u8>(data, offset)?;
191        result |= u64::from(byte & 127).checked_shl(shift)?;
192        shift += 7;
193
194        if byte & 128 == 0 {
195            return Some(result);
196        }
197    }
198}
199
200/// `readString`
201fn read_string(
202    strings: &TempBuffer<TString>,
203    data: &[u8],
204    offset: &mut usize,
205) -> Option<Option<TString>> {
206    let id = read_var_int(data, offset)?;
207    if id == 0 {
208        Some(None)
209    } else {
210        let index = id as usize - 1;
211        if index >= strings.count {
212            None
213        } else {
214            Some(Some(unsafe { *strings.data.add(index) }))
215        }
216    }
217}
218
219/// `ResolveImport::run`
220unsafe fn resolve_import_callback(thread: &Thread, context: &mut ResolveImportContext) -> VmResult {
221    unsafe {
222        thread.check_stack_internal(1)?;
223        let top = thread.stack_top();
224        thread.expand_stack_limit(top.add(1));
225        top.value_unchecked().set_nil();
226        thread.set_stack_top(top.add(1));
227
228        thread.get_import(
229            context.environment,
230            context.constants,
231            top,
232            context.id,
233            true,
234        )?;
235    }
236    Ok(())
237}
238
239/// `resolveImportSafe`
240fn resolve_import_safe(
241    thread: &Thread,
242    constants: TValueCursor,
243    environment: Table,
244    id: u32,
245) -> VmResult {
246    if unsafe { environment.as_ptr().as_ref().unwrap_unchecked().safe_env != 0 } {
247        let mut context = ResolveImportContext {
248            constants,
249            environment,
250            id,
251        };
252        let (old_top, result) = unsafe {
253            let old_top = thread.save_stack(thread.stack_top());
254            let result =
255                thread.protected_call_internal(resolve_import_callback, &mut context, old_top, 0);
256            (old_top, result)
257        };
258
259        debug_assert_eq!(
260            unsafe {
261                thread
262                    .stack_top()
263                    .offset_from(thread.restore_stack(old_top))
264            },
265            1
266        );
267
268        if let Err(exit) = result {
269            let VmExit::Error(_) = exit else {
270                return result;
271            };
272
273            unsafe { thread.stack_top().sub(1).value_unchecked().set_nil() }
274        }
275    } else {
276        unsafe {
277            let top = thread.stack_top();
278            top.value_unchecked().set_nil();
279            thread.set_stack_top(top.add(1));
280        }
281    }
282
283    Ok(())
284}
285
286fn malformed_bytecode(thread: &Thread, chunk_name: &[u8]) -> VmResult<i32> {
287    let mut chunk_buffer = [0u8; LUA_ID_SIZE];
288    let chunk_id = chunk_id(&mut chunk_buffer, chunk_name);
289    unsafe { crate::push_fstring!(thread, "%s: malformed bytecode", &chunk_id)? };
290    Ok(1)
291}
292
293fn malformed_constant_kind(thread: &Thread, chunk_name: &[u8], kind: u8) -> VmResult<i32> {
294    let mut chunk_buffer = [0u8; LUA_ID_SIZE];
295    let chunk_id = chunk_id(&mut chunk_buffer, chunk_name);
296    unsafe {
297        crate::push_fstring!(
298            thread,
299            "%s: malformed bytecode (unexpected constant kind %d)",
300            &chunk_id,
301            i32::from(kind)
302        )?;
303    }
304    Ok(1)
305}
306
307fn import_id_is_valid(id: u32, constant_count: usize) -> bool {
308    let count = (id >> 30) as usize;
309    if !(1..=3).contains(&count) {
310        return false;
311    }
312
313    let id0 = ((id >> 20) & 1023) as usize;
314    let id1 = ((id >> 10) & 1023) as usize;
315    let id2 = (id & 1023) as usize;
316
317    id0 < constant_count
318        && (count < 2 || id1 < constant_count)
319        && (count < 3 || id2 < constant_count)
320}
321
322fn line_info_layout(size_code: i32, line_gap_log2: u8) -> Option<(usize, usize, usize)> {
323    let size_code = usize::try_from(size_code).ok()?;
324    if size_code == 0 || u32::from(line_gap_log2) >= i32::BITS {
325        return None;
326    }
327
328    let intervals = ((size_code - 1) >> line_gap_log2).checked_add(1)?;
329    let abs_offset = size_code.checked_add(3)? & !3usize;
330    let size_line_info = abs_offset.checked_add(intervals.checked_mul(size_of::<i32>())?)?;
331    (size_line_info <= i32::MAX as usize).then_some((intervals, abs_offset, size_line_info))
332}
333
334/// `remapUserdataTypes`
335fn remap_userdata_types(data: *mut u8, size: usize, remapping: &[u8], count: usize) -> bool {
336    let data = unsafe { core::slice::from_raw_parts_mut(data, size) };
337    let mut offset = 0usize;
338
339    let Some(type_size) = read_var_int(data, &mut offset) else {
340        return false;
341    };
342    let Some(upvalue_count) = read_var_int(data, &mut offset) else {
343        return false;
344    };
345    let Some(local_count) = read_var_int(data, &mut offset) else {
346        return false;
347    };
348
349    if type_size != 0 {
350        let Some(types) = read_slice_mut(data, &mut offset, type_size as usize) else {
351            return false;
352        };
353
354        for ty in types.iter_mut().skip(2) {
355            let index = usize::from(ty.wrapping_sub(BytecodeTypeTag::TaggedUserdataBase as u8));
356            if index < count {
357                *ty = remapping[index];
358            }
359        }
360    }
361
362    if upvalue_count != 0 {
363        let Some(types) = read_slice_mut(data, &mut offset, upvalue_count as usize) else {
364            return false;
365        };
366
367        for ty in types {
368            let index = usize::from(ty.wrapping_sub(BytecodeTypeTag::TaggedUserdataBase as u8));
369            if index < count {
370                *ty = remapping[index];
371            }
372        }
373    }
374
375    for _ in 0..local_count {
376        let Some(ty) = data.get_mut(offset) else {
377            return false;
378        };
379        let index = usize::from((*ty).wrapping_sub(BytecodeTypeTag::TaggedUserdataBase as u8));
380
381        if index < count {
382            *ty = remapping[index];
383        }
384
385        let Some(next_offset) = offset.checked_add(2) else {
386            return false;
387        };
388        if next_offset > data.len() {
389            return false;
390        }
391
392        offset = next_offset;
393        if read_var_int(data, &mut offset).is_none() || read_var_int(data, &mut offset).is_none() {
394            return false;
395        }
396    }
397
398    offset == size
399}
400
401/// `loadsafe`
402fn load_safe(thread: &Thread, context: &mut LoadContext<'_>) -> VmResult<i32> {
403    let data = context.data;
404    let mut offset = 0usize;
405
406    macro_rules! malformed {
407        () => {
408            return malformed_bytecode(thread, context.chunk_name)
409        };
410    }
411
412    macro_rules! read_value {
413        ($ty:ty) => {
414            match read::<$ty>(data, &mut offset) {
415                Some(value) => value,
416                None => malformed!(),
417            }
418        };
419    }
420
421    macro_rules! read_var_u32 {
422        () => {
423            match read_var_int(data, &mut offset) {
424                Some(value) => value,
425                None => malformed!(),
426            }
427        };
428    }
429
430    macro_rules! read_var_u64 {
431        () => {
432            match read_var_int64(data, &mut offset) {
433                Some(value) => value,
434                None => malformed!(),
435            }
436        };
437    }
438
439    macro_rules! read_bytes {
440        ($len:expr) => {
441            match read_slice(data, &mut offset, $len) {
442                Some(bytes) => bytes,
443                None => malformed!(),
444            }
445        };
446    }
447
448    macro_rules! read_string_value {
449        () => {
450            match read_string(&context.strings, data, &mut offset) {
451                Some(value) => value,
452                None => malformed!(),
453            }
454        };
455    }
456
457    let version = read_value!(u8);
458    if version == 0 {
459        let mut chunk_buffer = [0u8; LUA_ID_SIZE];
460        let chunk_id = chunk_id(&mut chunk_buffer, context.chunk_name);
461        let remaining = &data[offset..];
462        unsafe {
463            crate::push_fstring!(
464                thread,
465                "%s%.*s",
466                &chunk_id,
467                remaining.len() as i32,
468                remaining
469            )?
470        };
471        return Ok(1);
472    }
473
474    if !(BYTECODE_VERSION_MIN..=BYTECODE_VERSION_MAX).contains(&version)
475        && version != BYTECODE_VERSION_CLASSES
476    {
477        let mut chunk_buffer = [0u8; LUA_ID_SIZE];
478        let chunk_id = chunk_id(&mut chunk_buffer, context.chunk_name);
479        unsafe {
480            crate::push_fstring!(
481                thread,
482                "%s: bytecode version mismatch (expected [%d..%d], got %d)",
483                &chunk_id,
484                BYTECODE_VERSION_MIN as i32,
485                BYTECODE_VERSION_MAX as i32,
486                version as i32
487            )?
488        };
489        return Ok(1);
490    }
491
492    let mut type_version = 0u8;
493    if version >= 4 {
494        type_version = read_value!(u8);
495
496        if !(BYTECODE_TYPE_VERSION_MIN..=BYTECODE_TYPE_VERSION_MAX).contains(&type_version) {
497            let mut chunk_buffer = [0u8; LUA_ID_SIZE];
498            let chunk_id = chunk_id(&mut chunk_buffer, context.chunk_name);
499            unsafe {
500                crate::push_fstring!(
501                    thread,
502                    "%s: bytecode type version mismatch (expected [%d..%d], got %d)",
503                    &chunk_id,
504                    BYTECODE_TYPE_VERSION_MIN as i32,
505                    BYTECODE_TYPE_VERSION_MAX as i32,
506                    type_version as i32
507                )?
508            };
509            return Ok(1);
510        }
511    }
512
513    let globals = unsafe { thread.globals() };
514    let env_table = if context.env == 0 {
515        globals
516    } else {
517        unsafe {
518            let env = thread.to_object(context.env);
519            debug_assert!(env.is_some());
520            let env = env.unwrap_unchecked();
521            debug_assert!(env.is_table());
522            env.table_value()
523        }
524    };
525
526    let source = unsafe { thread.intern_string(context.chunk_name.as_bstr())? };
527
528    let string_count = read_var_u32!() as usize;
529    unsafe { context.strings.allocate(thread, string_count)? };
530
531    for index in 0..string_count {
532        let len = read_var_u32!() as usize;
533        let bytes = read_bytes!(len);
534        let string = unsafe { thread.intern_string(bytes.as_bstr())? };
535
536        unsafe {
537            *context.strings.data.add(index) = string;
538        }
539    }
540
541    let mut userdata_remapping = [BytecodeTypeTag::Userdata as u8; USERDATA_TYPE_LIMIT];
542
543    if type_version == 3 {
544        let mut index = read_value!(u8);
545
546        while index != 0 {
547            let name = read_string_value!();
548
549            if usize::from(index - 1) < USERDATA_TYPE_LIMIT
550                && let Some(callback) = unsafe { thread.global().execution_type_mapping() }
551                && let Some(name) = name
552            {
553                userdata_remapping[usize::from(index - 1)] =
554                    unsafe { callback(thread, name.as_bytes().as_bstr()) };
555            }
556
557            index = read_value!(u8);
558        }
559    }
560
561    let proto_count = read_var_u32!() as usize;
562    unsafe { context.protos.allocate(thread, proto_count)? };
563
564    for index in 0..proto_count {
565        let proto_size = if version >= 12 {
566            read_var_u32!() as usize
567        } else {
568            0
569        };
570        let proto_start_offset = offset;
571        let proto = unsafe { thread.new_proto()? };
572
573        unsafe {
574            let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
575            proto_ref.source = source.as_ptr();
576            proto_ref.bytecode_id = index as i32;
577
578            let global_handle = thread.global();
579            let global = global_handle.as_ptr().as_mut().unwrap_unchecked();
580            proto_ref.fun_id = if global.last_proto_id == 0 {
581                0
582            } else {
583                let result = global.last_proto_id;
584                global.last_proto_id += 1;
585                result
586            };
587
588            proto_ref.max_stack_size = read_value!(u8);
589            proto_ref.num_params = read_value!(u8);
590            proto_ref.n_ups = read_value!(u8);
591            proto_ref.is_vararg = read_value!(u8);
592        }
593
594        if version >= 4 {
595            unsafe {
596                proto.as_ptr().as_mut().unwrap_unchecked().flags = read_value!(u8);
597            }
598
599            if type_version == 1 {
600                let type_size = read_var_u32!() as usize;
601
602                if type_size != 0 {
603                    let types = read_bytes!(type_size);
604                    if type_size
605                        != 2 + unsafe {
606                            proto.as_ptr().as_ref().unwrap_unchecked().num_params as usize
607                        }
608                        || types.first().copied() != Some(BytecodeTypeTag::Function as u8)
609                        || types.get(1).copied()
610                            != Some(unsafe {
611                                proto.as_ptr().as_ref().unwrap_unchecked().num_params
612                            })
613                    {
614                        malformed!();
615                    }
616
617                    let header_size = if type_size > 127 { 4 } else { 3 };
618                    let total_size = header_size + type_size;
619                    let allocated = unsafe {
620                        thread.new_array::<u8>(
621                            total_size,
622                            proto.as_ptr().as_ref().unwrap_unchecked().memcat,
623                        )?
624                    };
625
626                    unsafe {
627                        let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
628                        proto_ref.type_info = allocated;
629                        proto_ref.size_type_info = total_size as i32;
630
631                        if header_size == 4 {
632                            *allocated.add(0) = ((type_size & 127) as u8) | (1 << 7);
633                            *allocated.add(1) = (type_size >> 7) as u8;
634                            *allocated.add(2) = 0;
635                            *allocated.add(3) = 0;
636                        } else {
637                            *allocated.add(0) = type_size as u8;
638                            *allocated.add(1) = 0;
639                            *allocated.add(2) = 0;
640                        }
641
642                        ptr::copy_nonoverlapping(
643                            types.as_ptr(),
644                            allocated.add(header_size),
645                            type_size,
646                        );
647                    }
648                }
649            } else if type_version == 2 || type_version == 3 {
650                let type_size = read_var_u32!() as usize;
651
652                if type_size != 0 {
653                    let types = read_bytes!(type_size);
654                    let allocated = unsafe {
655                        thread.new_array::<u8>(
656                            type_size,
657                            proto.as_ptr().as_ref().unwrap_unchecked().memcat,
658                        )?
659                    };
660
661                    unsafe {
662                        let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
663                        proto_ref.type_info = allocated;
664                        proto_ref.size_type_info = type_size as i32;
665                        ptr::copy_nonoverlapping(types.as_ptr(), allocated, type_size);
666                    }
667
668                    if type_version == 3 {
669                        if unsafe {
670                            remap_userdata_types(
671                                proto.as_ptr().as_ref().unwrap_unchecked().type_info,
672                                proto.as_ptr().as_ref().unwrap_unchecked().size_type_info as usize,
673                                &userdata_remapping,
674                                USERDATA_TYPE_LIMIT,
675                            )
676                        } {
677                            // remapping completed in place
678                        } else {
679                            malformed!();
680                        }
681                    }
682                }
683            }
684        }
685
686        let size_code = read_var_u32!() as usize;
687        if size_code != 0 {
688            let code = unsafe {
689                thread.new_array::<u32>(
690                    size_code,
691                    proto.as_ptr().as_ref().unwrap_unchecked().memcat,
692                )?
693            };
694
695            unsafe {
696                let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
697                proto_ref.code = code;
698                proto_ref.size_code = size_code as i32;
699
700                for word in 0..size_code {
701                    *proto_ref.code.add(word) = read_value!(u32);
702                }
703
704                proto_ref.code_entry = proto_ref.code;
705            }
706        }
707
708        let size_k = read_var_u32!() as usize;
709        if size_k != 0 {
710            let constants = unsafe {
711                thread.new_array::<RawTValue>(
712                    size_k,
713                    proto.as_ptr().as_ref().unwrap_unchecked().memcat,
714                )?
715            };
716
717            unsafe {
718                let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
719                proto_ref.k = constants;
720                proto_ref.size_k = size_k as i32;
721
722                let constants = TValueCursor::from_ptr(proto_ref.k);
723                for constant_index in 0..size_k {
724                    constants.add(constant_index).value_unchecked().set_nil();
725                }
726            }
727        }
728
729        for constant_index in
730            0..unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_k as usize }
731        {
732            let constant = unsafe { proto.constant(constant_index) };
733            let constant_count =
734                unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_k as usize };
735
736            match read_value!(u8) {
737                tag if tag == BytecodeConstantTag::Nil as u8 => {}
738                tag if tag == BytecodeConstantTag::Boolean as u8 => {
739                    constant.set_boolean(i32::from(read_value!(u8)));
740                }
741                tag if tag == BytecodeConstantTag::Number as u8 => {
742                    constant.set_number(read_value!(f64));
743                }
744                tag if tag == BytecodeConstantTag::Vector as u8 => {
745                    let x = read_value!(f32);
746                    let y = read_value!(f32);
747                    let z = read_value!(f32);
748                    let w = read_value!(f32);
749                    #[cfg(not(feature = "vector4"))]
750                    {
751                        let _ = w;
752                        constant.set_vector([x, y, z]);
753                    }
754                    #[cfg(feature = "vector4")]
755                    constant.set_vector([x, y, z, w]);
756                }
757                tag if tag == BytecodeConstantTag::VectorDouble as u8 => {
758                    let x = read_value!(f64) as f32;
759                    let y = read_value!(f64) as f32;
760                    let z = read_value!(f64) as f32;
761                    let w = read_value!(f64) as f32;
762                    #[cfg(not(feature = "vector4"))]
763                    {
764                        let _ = w;
765                        constant.set_vector([x, y, z]);
766                    }
767                    #[cfg(feature = "vector4")]
768                    constant.set_vector([x, y, z, w]);
769                }
770                tag if tag == BytecodeConstantTag::String as u8 => {
771                    let Some(value) = read_string_value!() else {
772                        malformed!();
773                    };
774                    constant.set_string_value(value);
775                }
776                tag if tag == BytecodeConstantTag::Import as u8 => {
777                    let id = read_value!(u32);
778                    if !import_id_is_valid(id, constant_count) {
779                        malformed!();
780                    }
781                    resolve_import_safe(thread, unsafe { proto.constants() }, env_table, id)?;
782                    unsafe {
783                        let top = thread.stack_top();
784                        constant.set_obj(top.sub(1).value_unchecked());
785                        thread.set_stack_top(top.sub(1));
786                    }
787                }
788                tag if tag == BytecodeConstantTag::Table as u8 => {
789                    let keys = read_var_u32!() as usize;
790                    let table = unsafe { thread.new_table_internal(0, keys as i32)? };
791
792                    for _ in 0..keys {
793                        let key = read_var_u32!() as usize;
794                        if key >= constant_count {
795                            malformed!();
796                        }
797                        let slot = unsafe { thread.set(table, proto.constant(key))? };
798                        slot.set_number(0.0);
799                    }
800
801                    constant.set_table_value(table);
802                }
803                tag if tag == BytecodeConstantTag::TableWithConstants as u8 => {
804                    let keys = read_var_u32!() as usize;
805                    let table = unsafe { thread.new_table_internal(0, keys as i32)? };
806                    let mut nil_keys = TempBuffer::<i32>::new();
807                    unsafe { nil_keys.allocate(thread, keys)? };
808                    let mut nil_keys_size = 0usize;
809
810                    for _ in 0..keys {
811                        let key = read_var_u32!() as usize;
812                        if key >= constant_count {
813                            malformed!();
814                        }
815                        let slot = unsafe { thread.set(table, proto.constant(key))? };
816                        let constant_index = read_value!(i32);
817
818                        if constant_index >= 0 {
819                            if constant_index as usize >= constant_count {
820                                malformed!();
821                            }
822                            let constant_value = unsafe { proto.constant(constant_index as usize) };
823
824                            if constant_value.is_nil() {
825                                unsafe {
826                                    *nil_keys.data.add(nil_keys_size) = key as i32;
827                                }
828                                nil_keys_size += 1;
829                            } else {
830                                slot.set_obj(constant_value);
831
832                                if constant_value.is_collectable() {
833                                    let table_object: GcObject = table.into();
834                                    let child = constant_value.gc_value();
835
836                                    if unsafe { table_object.is_black() }
837                                        && unsafe { child.is_white() }
838                                    {
839                                        unsafe { thread.barrier_table(table, child) };
840                                    }
841                                }
842
843                                continue;
844                            }
845                        }
846
847                        slot.set_number(0.0);
848                    }
849
850                    for nil_index in 0..nil_keys_size {
851                        let key = unsafe { *nil_keys.data.add(nil_index) } as usize;
852                        let slot = unsafe { thread.set(table, proto.constant(key))? };
853                        slot.set_nil();
854                    }
855
856                    constant.set_table_value(table);
857                }
858                tag if tag == BytecodeConstantTag::Closure as u8 => {
859                    let function_id = read_var_u32!() as usize;
860                    if function_id >= index {
861                        malformed!();
862                    }
863                    let child = unsafe { *context.protos.data.add(function_id) };
864                    let closure = unsafe {
865                        thread.new_lua_closure(
866                            i32::from(child.as_ptr().as_ref().unwrap_unchecked().n_ups),
867                            Some(env_table),
868                            child,
869                        )?
870                    };
871
872                    unsafe {
873                        let closure_ref = closure.as_ptr().as_mut().unwrap_unchecked();
874                        closure_ref.preload = u8::from(closure_ref.n_upvalues > 0);
875                        constant.set_closure_value(closure);
876                    }
877                }
878                tag if tag == BytecodeConstantTag::ClassShape as u8 => {
879                    let class_name_id = read_var_u32!() as usize;
880                    if class_name_id >= constant_count {
881                        malformed!();
882                    }
883                    let class_name = unsafe { proto.constant(class_name_id) };
884                    if !class_name.is_string() {
885                        malformed!();
886                    }
887
888                    let number_of_instance_members = read_var_u32!();
889                    let number_of_static_members = read_var_u32!();
890                    let Some(number_of_members) =
891                        number_of_instance_members.checked_add(number_of_static_members)
892                    else {
893                        malformed!();
894                    };
895                    let Ok(number_of_members) = usize::try_from(number_of_members) else {
896                        malformed!();
897                    };
898                    let Ok(table_capacity) = i32::try_from(number_of_members) else {
899                        malformed!();
900                    };
901
902                    // The source loader trusts compiler-produced class shapes. This loader also
903                    // accepts arbitrary bytes through the safe Rust API, so validate every member
904                    // before allocating the unowned offset array below.
905                    let mut members_end = offset;
906                    for _ in 0..number_of_members {
907                        let Some(member_id) = read_var_int(data, &mut members_end) else {
908                            malformed!();
909                        };
910                        let member_id = member_id as usize;
911                        if member_id >= constant_count {
912                            malformed!();
913                        }
914                        let member_name = unsafe { proto.constant(member_id) };
915                        if !member_name.is_string() {
916                            malformed!();
917                        }
918                    }
919
920                    let offset_to_member = unsafe {
921                        thread.new_array::<TString>(
922                            number_of_members,
923                            thread.as_ptr().as_ref().unwrap_unchecked().active_memcat,
924                        )?
925                    };
926                    let members_to_offset =
927                        unsafe { thread.new_table_internal(0, table_capacity)? };
928
929                    for member_index in 0..number_of_members {
930                        let member_id = read_var_u32!() as usize;
931                        if member_id >= constant_count {
932                            malformed!();
933                        }
934                        let member_name = unsafe { proto.constant(member_id) };
935                        if !member_name.is_string() {
936                            malformed!();
937                        }
938
939                        unsafe {
940                            *offset_to_member.add(member_index) = member_name.string_value();
941                        }
942
943                        let node_cursor = unsafe {
944                            thread.set_str(members_to_offset, member_name.string_value())?
945                        };
946                        unsafe {
947                            node_cursor
948                                .node_unchecked()
949                                .value_unchecked()
950                                .set_number(member_index as f64);
951                        }
952                    }
953                    debug_assert_eq!(offset, members_end);
954
955                    unsafe {
956                        members_to_offset
957                            .as_ptr()
958                            .as_mut()
959                            .unwrap_unchecked()
960                            .readonly = 1;
961                    }
962
963                    let class = unsafe {
964                        thread.new_class(
965                            class_name.string_value(),
966                            members_to_offset,
967                            offset_to_member,
968                            number_of_instance_members,
969                            number_of_static_members,
970                        )?
971                    };
972
973                    constant.set_class_value(class);
974                }
975                tag if tag == BytecodeConstantTag::Integer as u8 => {
976                    let is_negative = read_value!(u8) != 0;
977                    let magnitude = read_var_u64!();
978                    constant.set_integer(if is_negative {
979                        (!magnitude).wrapping_add(1) as i64
980                    } else {
981                        magnitude as i64
982                    });
983                }
984                other => return malformed_constant_kind(thread, context.chunk_name, other),
985            }
986        }
987
988        let userdata_direct_access_6 = flags::LuauUdataDirectAccess6.get();
989        let code = unsafe { proto.as_ptr().as_ref().unwrap_unchecked().code };
990        let size_code = unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_code as usize };
991        let mut instruction_index = 0;
992
993        while instruction_index < size_code {
994            let instruction = unsafe { code.add(instruction_index) };
995            let Ok(opcode) = (unsafe { Instruction::new(*instruction) }).try_opcode() else {
996                malformed!();
997            };
998
999            if userdata_direct_access_6 {
1000                let target_op = match opcode {
1001                    Opcode::GetTableKs => Some(Opcode::GetUDataKs),
1002                    Opcode::SetTableKs => Some(Opcode::SetUDataKs),
1003                    Opcode::NameCall => Some(Opcode::NameCallUData),
1004                    _ => None,
1005                };
1006
1007                if let Some(target_op) = target_op {
1008                    if size_code - instruction_index < 2 {
1009                        malformed!();
1010                    }
1011
1012                    let aux = unsafe { *instruction.add(1) } as usize;
1013                    if aux >= unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_k as usize }
1014                    {
1015                        malformed!();
1016                    }
1017
1018                    if aux < 0x10000 {
1019                        let constant = unsafe { proto.constant(aux) };
1020                        if !constant.is_string() {
1021                            malformed!();
1022                        }
1023                        let string = constant.string_value();
1024                        unsafe { thread.update_atom(string) };
1025
1026                        if unsafe { string.as_ptr().as_ref().unwrap_unchecked().atom } >= 0 {
1027                            unsafe {
1028                                *instruction = (*instruction & 0xffff_ff00) | target_op as u32;
1029                            }
1030                        }
1031                    }
1032                }
1033            }
1034
1035            let step = opcode.length();
1036            if step == 0 || step > size_code - instruction_index {
1037                malformed!();
1038            }
1039            instruction_index += step;
1040        }
1041
1042        let size_p = read_var_u32!() as usize;
1043        if size_p != 0 {
1044            let protos = unsafe {
1045                thread
1046                    .new_array::<Proto>(size_p, proto.as_ptr().as_ref().unwrap_unchecked().memcat)?
1047            };
1048
1049            unsafe {
1050                let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
1051                proto_ref.p = protos.cast::<*mut RawProto>();
1052                proto_ref.size_p = size_p as i32;
1053
1054                for child_index in 0..size_p {
1055                    let function_id = read_var_u32!() as usize;
1056                    if function_id >= index {
1057                        malformed!();
1058                    }
1059                    *protos.add(child_index) = *context.protos.data.add(function_id);
1060                    *proto_ref.p.add(child_index) = (*protos.add(child_index)).as_ptr();
1061                }
1062            }
1063        }
1064
1065        unsafe {
1066            let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
1067            proto_ref.line_defined = read_var_u32!() as i32;
1068            proto_ref.debug_name =
1069                read_string_value!().map_or(ptr::null_mut(), |string| string.as_ptr());
1070        }
1071
1072        if read_value!(u8) != 0 {
1073            unsafe {
1074                let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
1075                let line_gap_log2 = read_value!(u8);
1076                let Some((intervals, abs_offset, size_line_info)) =
1077                    line_info_layout(proto_ref.size_code, line_gap_log2)
1078                else {
1079                    malformed!();
1080                };
1081
1082                let bytes = thread.new_array::<u8>(size_line_info, proto_ref.memcat)?;
1083
1084                proto_ref.line_gap_log2 = i32::from(line_gap_log2);
1085                proto_ref.line_info = bytes;
1086                proto_ref.size_line_info = size_line_info as i32;
1087                proto_ref.abs_line_info = bytes.add(abs_offset).cast::<i32>();
1088
1089                let mut last_offset = 0u8;
1090                for line_index in 0..proto_ref.size_code as usize {
1091                    last_offset = last_offset.wrapping_add(read_value!(u8));
1092                    *proto_ref.line_info.add(line_index) = last_offset;
1093                }
1094
1095                let mut last_line = 0i32;
1096                for interval_index in 0..intervals {
1097                    let Some(line) = last_line.checked_add(read_value!(i32)) else {
1098                        malformed!();
1099                    };
1100                    last_line = line;
1101                    *proto_ref.abs_line_info.add(interval_index) = last_line;
1102                }
1103            }
1104        }
1105
1106        if read_value!(u8) != 0 {
1107            let size_locals = read_var_u32!() as usize;
1108            if size_locals != 0 {
1109                let locals = unsafe {
1110                    thread.new_array::<RawLocVar>(
1111                        size_locals,
1112                        proto.as_ptr().as_ref().unwrap_unchecked().memcat,
1113                    )?
1114                };
1115
1116                unsafe {
1117                    let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
1118                    proto_ref.loc_vars = locals;
1119                    proto_ref.size_loc_vars = size_locals as i32;
1120
1121                    for local_index in 0..size_locals {
1122                        let local = &mut *proto_ref.loc_vars.add(local_index);
1123                        local.var_name =
1124                            read_string_value!().map_or(ptr::null_mut(), |string| string.as_ptr());
1125                        local.start_pc = read_var_u32!() as i32;
1126                        local.end_pc = read_var_u32!() as i32;
1127                        local.reg = read_value!(u8);
1128                    }
1129                }
1130            }
1131
1132            let size_upvalues = read_var_u32!() as usize;
1133            if size_upvalues as u8 != unsafe { proto.as_ptr().as_ref().unwrap_unchecked().n_ups } {
1134                malformed!();
1135            }
1136
1137            if size_upvalues != 0 {
1138                let upvalues = unsafe {
1139                    thread.new_array::<*mut RawTString>(
1140                        size_upvalues,
1141                        proto.as_ptr().as_ref().unwrap_unchecked().memcat,
1142                    )?
1143                };
1144
1145                unsafe {
1146                    let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
1147                    proto_ref.upvalues = upvalues;
1148                    proto_ref.size_upvalues = size_upvalues as i32;
1149
1150                    for upvalue_index in 0..size_upvalues {
1151                        *proto_ref.upvalues.add(upvalue_index) =
1152                            read_string_value!().map_or(ptr::null_mut(), |string| string.as_ptr());
1153                    }
1154                }
1155            }
1156        }
1157
1158        if version >= 11 {
1159            let size = read_var_u32!() as usize;
1160            unsafe {
1161                proto.as_ptr().as_mut().unwrap_unchecked().feedback_vec_size = size as u32;
1162            }
1163
1164            if size != 0 {
1165                let feedback = unsafe {
1166                    thread.new_array::<FeedbackVectorSlot>(
1167                        size,
1168                        proto.as_ptr().as_ref().unwrap_unchecked().memcat,
1169                    )?
1170                };
1171
1172                unsafe {
1173                    let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
1174                    proto_ref.feedback_vec = feedback;
1175
1176                    for slot_index in 0..size {
1177                        let slot_type = read_value!(u8);
1178                        if slot_type != FEEDBACK_TYPE_CALLTARGET {
1179                            malformed!();
1180                        }
1181
1182                        *proto_ref.feedback_vec.add(slot_index) = FeedbackVectorSlot {
1183                            kind: i32::from(slot_type),
1184                            data: FeedbackVectorSlotData {
1185                                call_target: FeedbackVectorSlotCallTarget {
1186                                    pc: read_var_u32!(),
1187                                    proto: 0,
1188                                    hits: 0,
1189                                },
1190                            },
1191                        };
1192                    }
1193                }
1194            }
1195        }
1196
1197        if version >= 12
1198            && unsafe { proto.as_ptr().as_ref().unwrap_unchecked().flags } & PROTO_FLAG_INLINABLE
1199                != 0
1200        {
1201            unsafe {
1202                proto.as_ptr().as_mut().unwrap_unchecked().cost = read_var_u64!();
1203            }
1204        }
1205
1206        if version >= 12 {
1207            let Some(proto_end_offset) = proto_start_offset.checked_add(proto_size) else {
1208                malformed!();
1209            };
1210            if proto_end_offset > data.len() || proto_end_offset < offset {
1211                malformed!();
1212            }
1213            offset = proto_end_offset;
1214        }
1215
1216        unsafe {
1217            *context.protos.data.add(index) = proto;
1218        }
1219    }
1220
1221    let main_id = read_var_u32!() as usize;
1222    if main_id >= context.protos.count {
1223        malformed!();
1224    }
1225    let main_proto = unsafe { *context.protos.data.add(main_id) };
1226
1227    unsafe { thread.thread_barrier() };
1228
1229    let closure = unsafe { thread.new_lua_closure(0, Some(env_table), main_proto)? };
1230    let top = unsafe { thread.stack_top() };
1231    unsafe {
1232        top.value_unchecked().set_closure_value(closure);
1233        thread.set_stack_top(top.add(1));
1234    }
1235
1236    Ok(0)
1237}
1238
1239/// `LoadContext::run`
1240unsafe fn load_callback(thread: &Thread, context: &mut LoadContext<'_>) -> VmResult {
1241    if load_safe(thread, context)? != 0 {
1242        return Err(VmError::Syntax.into());
1243    }
1244    Ok(())
1245}
1246
1247impl Thread {
1248    /// `luau_load`
1249    ///
1250    /// # Safety
1251    ///
1252    /// The thread and its VM must remain live and satisfy [`Thread`]'s shared
1253    /// unsafe API contract. `env` must identify a valid environment table or
1254    /// accepted pseudo-index for this VM, and `data` must be bytecode for the
1255    /// targeted Luau release.
1256    pub unsafe fn load(
1257        &self,
1258        chunk_name: impl AsRef<[u8]>,
1259        data: impl AsRef<[u8]>,
1260        env: i32,
1261    ) -> VmResult {
1262        unsafe { self.load_bytecode(chunk_name.as_ref(), data.as_ref(), env) }
1263    }
1264
1265    pub(crate) unsafe fn load_bytecode(
1266        &self,
1267        chunk_name: &[u8],
1268        data: &[u8],
1269        env: i32,
1270    ) -> VmResult {
1271        unsafe {
1272            let _pause_gc = {
1273                self.check_gc()?;
1274                ScopedGcThreshold::new(self.global(), usize::MAX)
1275            };
1276
1277            let mut context = LoadContext {
1278                strings: TempBuffer::new(),
1279                protos: TempBuffer::new(),
1280                chunk_name,
1281                data,
1282                env,
1283            };
1284
1285            match self.raw_run_protected(load_callback, &mut context) {
1286                Ok(()) => Ok(()),
1287                Err(error) => {
1288                    if let VmExit::Error(VmError::Memory) = error {
1289                        let top = self.stack_top();
1290                        top.value_unchecked().set_string_value(
1291                            self.intern_string(LUA_MEMERRMSG.as_bstr()).expect(
1292                                "memory error message is fixed during state initialization",
1293                            ),
1294                        );
1295                        self.set_stack_top(top.add(1));
1296                    }
1297                    Err(error)
1298                }
1299            }
1300        }
1301    }
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306    use super::{line_info_layout, remap_userdata_types};
1307
1308    #[test]
1309    fn line_info_layout_rejects_invalid_code_sizes_and_shifts() {
1310        assert_eq!(line_info_layout(2, 0), Some((2, 4, 12)));
1311        assert_eq!(line_info_layout(0, 0), None);
1312        assert_eq!(line_info_layout(2, 32), None);
1313    }
1314
1315    #[test]
1316    fn userdata_type_remapping_rejects_trailing_data() {
1317        let mut data = [0, 0, 0, 0];
1318        assert!(!remap_userdata_types(data.as_mut_ptr(), data.len(), &[], 0));
1319    }
1320}