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 const fn new() -> Self {
50 Self {
51 thread: ptr::null(),
52 data: ptr::null_mut(),
53 count: 0,
54 }
55 }
56
57 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 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 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 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
129fn 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
160fn 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
180fn 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
200fn 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
219unsafe 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
239fn 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
334fn 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
401fn 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 } 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 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
1239unsafe 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 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}