use core::mem::{MaybeUninit, size_of};
use core::ptr;
use luau_bytecode::model::Instruction;
use luau_bytecode::opcodes::{
BYTECODE_TYPE_VERSION_MAX, BYTECODE_TYPE_VERSION_MIN, BYTECODE_VERSION_CLASSES,
BYTECODE_VERSION_MAX, BYTECODE_VERSION_MIN, BytecodeConstantTag, BytecodeTypeTag,
FEEDBACK_TYPE_CALLTARGET, Opcode, PROTO_FLAG_INLINABLE,
};
use luau_common::{ByteSlice, flags};
use crate::Table;
use crate::call::{ProtectedCall, ThreadStack};
use crate::class::ClassRuntime;
use crate::debug::LUA_ID_SIZE;
use crate::debug::chunk_id;
use crate::function::FunctionRuntime;
use crate::function::Proto;
use crate::function::{
FeedbackVectorSlot, FeedbackVectorSlotCallTarget, FeedbackVectorSlotData, RawLocVar, RawProto,
};
use crate::gc::GcObject;
use crate::gc::{GcBarrier, GcRuntime};
use crate::handle::RawHandle;
use crate::memory::MemoryRuntime;
use crate::state::ThreadState;
use crate::state::{GlobalState, LUA_MEMERRMSG};
use crate::string::RawTString;
use crate::string::StringRuntime;
use crate::string::TString;
use crate::table::TableRuntime;
use crate::thread::Thread;
use crate::thread::stack::RawStackAccess;
use crate::value::{RawTValue, TValueCursor};
use crate::vm::VmOperations;
use crate::{VmError, VmExit, VmResult};
const USERDATA_TYPE_LIMIT: usize =
BytecodeTypeTag::TaggedUserdataEnd as usize - BytecodeTypeTag::TaggedUserdataBase as usize;
struct TempBuffer<T> {
thread: *const Thread,
data: *mut T,
count: usize,
}
impl<T> TempBuffer<T> {
const fn new() -> Self {
Self {
thread: ptr::null(),
data: ptr::null_mut(),
count: 0,
}
}
unsafe fn allocate(&mut self, thread: &Thread, count: usize) -> VmResult {
debug_assert!(self.thread.is_null());
self.thread = ptr::from_ref(thread);
self.count = count;
if count == 0 {
self.data = ptr::null_mut();
return Ok(());
}
self.data = unsafe { thread.new_array::<T>(count, 0)? };
Ok(())
}
}
impl<T> Drop for TempBuffer<T> {
fn drop(&mut self) {
if !self.thread.is_null() && !self.data.is_null() {
unsafe { (&*self.thread).free_array(self.data, self.count, 0) };
}
}
}
struct ScopedGcThreshold {
global: GlobalState,
original_threshold: usize,
}
impl ScopedGcThreshold {
unsafe fn new(global: GlobalState, threshold: usize) -> Self {
let original_threshold =
unsafe { global.as_ptr().as_ref().unwrap_unchecked().gc_threshold };
unsafe { global.as_ptr().as_mut().unwrap_unchecked().gc_threshold = threshold };
Self {
global,
original_threshold,
}
}
}
impl Drop for ScopedGcThreshold {
fn drop(&mut self) {
unsafe {
self.global
.as_ptr()
.as_mut()
.unwrap_unchecked()
.gc_threshold = self.original_threshold
};
}
}
struct LoadContext<'a> {
strings: TempBuffer<TString>,
protos: TempBuffer<Proto>,
chunk_name: &'a [u8],
data: &'a [u8],
env: i32,
}
#[repr(C)]
struct ResolveImportContext {
constants: TValueCursor,
environment: Table,
id: u32,
}
fn read<T: Copy>(data: &[u8], offset: &mut usize) -> Option<T> {
let end = offset.checked_add(size_of::<T>())?;
let bytes = data.get(*offset..end)?;
let mut value = MaybeUninit::<T>::uninit();
unsafe {
ptr::copy_nonoverlapping(
bytes.as_ptr(),
value.as_mut_ptr().cast::<u8>(),
size_of::<T>(),
);
*offset = end;
Some(value.assume_init())
}
}
fn read_slice<'a>(data: &'a [u8], offset: &mut usize, len: usize) -> Option<&'a [u8]> {
let end = offset.checked_add(len)?;
let bytes = data.get(*offset..end)?;
*offset = end;
Some(bytes)
}
fn read_slice_mut<'a>(data: &'a mut [u8], offset: &mut usize, len: usize) -> Option<&'a mut [u8]> {
let end = offset.checked_add(len)?;
let bytes = data.get_mut(*offset..end)?;
*offset = end;
Some(bytes)
}
fn read_var_int(data: &[u8], offset: &mut usize) -> Option<u32> {
let mut result = 0u32;
let mut shift = 0u32;
loop {
if shift >= u32::BITS {
return None;
}
let byte = read::<u8>(data, offset)?;
result |= u32::from(byte & 127).checked_shl(shift)?;
shift += 7;
if byte & 128 == 0 {
return Some(result);
}
}
}
fn read_var_int64(data: &[u8], offset: &mut usize) -> Option<u64> {
let mut result = 0u64;
let mut shift = 0u32;
loop {
if shift >= u64::BITS {
return None;
}
let byte = read::<u8>(data, offset)?;
result |= u64::from(byte & 127).checked_shl(shift)?;
shift += 7;
if byte & 128 == 0 {
return Some(result);
}
}
}
fn read_string(
strings: &TempBuffer<TString>,
data: &[u8],
offset: &mut usize,
) -> Option<Option<TString>> {
let id = read_var_int(data, offset)?;
if id == 0 {
Some(None)
} else {
let index = id as usize - 1;
if index >= strings.count {
None
} else {
Some(Some(unsafe { *strings.data.add(index) }))
}
}
}
unsafe fn resolve_import_callback(thread: &Thread, context: &mut ResolveImportContext) -> VmResult {
unsafe {
thread.check_stack_internal(1)?;
let top = thread.stack_top();
thread.expand_stack_limit(top.add(1));
top.value_unchecked().set_nil();
thread.set_stack_top(top.add(1));
thread.get_import(
context.environment,
context.constants,
top,
context.id,
true,
)?;
}
Ok(())
}
fn resolve_import_safe(
thread: &Thread,
constants: TValueCursor,
environment: Table,
id: u32,
) -> VmResult {
if unsafe { environment.as_ptr().as_ref().unwrap_unchecked().safe_env != 0 } {
let mut context = ResolveImportContext {
constants,
environment,
id,
};
let (old_top, result) = unsafe {
let old_top = thread.save_stack(thread.stack_top());
let result =
thread.protected_call_internal(resolve_import_callback, &mut context, old_top, 0);
(old_top, result)
};
debug_assert_eq!(
unsafe {
thread
.stack_top()
.offset_from(thread.restore_stack(old_top))
},
1
);
if let Err(exit) = result {
let VmExit::Error(_) = exit else {
return result;
};
unsafe { thread.stack_top().sub(1).value_unchecked().set_nil() }
}
} else {
unsafe {
let top = thread.stack_top();
top.value_unchecked().set_nil();
thread.set_stack_top(top.add(1));
}
}
Ok(())
}
fn malformed_bytecode(thread: &Thread, chunk_name: &[u8]) -> VmResult<i32> {
let mut chunk_buffer = [0u8; LUA_ID_SIZE];
let chunk_id = chunk_id(&mut chunk_buffer, chunk_name);
unsafe { crate::push_fstring!(thread, "%s: malformed bytecode", &chunk_id)? };
Ok(1)
}
fn malformed_constant_kind(thread: &Thread, chunk_name: &[u8], kind: u8) -> VmResult<i32> {
let mut chunk_buffer = [0u8; LUA_ID_SIZE];
let chunk_id = chunk_id(&mut chunk_buffer, chunk_name);
unsafe {
crate::push_fstring!(
thread,
"%s: malformed bytecode (unexpected constant kind %d)",
&chunk_id,
i32::from(kind)
)?;
}
Ok(1)
}
fn import_id_is_valid(id: u32, constant_count: usize) -> bool {
let count = (id >> 30) as usize;
if !(1..=3).contains(&count) {
return false;
}
let id0 = ((id >> 20) & 1023) as usize;
let id1 = ((id >> 10) & 1023) as usize;
let id2 = (id & 1023) as usize;
id0 < constant_count
&& (count < 2 || id1 < constant_count)
&& (count < 3 || id2 < constant_count)
}
fn line_info_layout(size_code: i32, line_gap_log2: u8) -> Option<(usize, usize, usize)> {
let size_code = usize::try_from(size_code).ok()?;
if size_code == 0 || u32::from(line_gap_log2) >= i32::BITS {
return None;
}
let intervals = ((size_code - 1) >> line_gap_log2).checked_add(1)?;
let abs_offset = size_code.checked_add(3)? & !3usize;
let size_line_info = abs_offset.checked_add(intervals.checked_mul(size_of::<i32>())?)?;
(size_line_info <= i32::MAX as usize).then_some((intervals, abs_offset, size_line_info))
}
fn remap_userdata_types(data: *mut u8, size: usize, remapping: &[u8], count: usize) -> bool {
let data = unsafe { core::slice::from_raw_parts_mut(data, size) };
let mut offset = 0usize;
let Some(type_size) = read_var_int(data, &mut offset) else {
return false;
};
let Some(upvalue_count) = read_var_int(data, &mut offset) else {
return false;
};
let Some(local_count) = read_var_int(data, &mut offset) else {
return false;
};
if type_size != 0 {
let Some(types) = read_slice_mut(data, &mut offset, type_size as usize) else {
return false;
};
for ty in types.iter_mut().skip(2) {
let index = usize::from(ty.wrapping_sub(BytecodeTypeTag::TaggedUserdataBase as u8));
if index < count {
*ty = remapping[index];
}
}
}
if upvalue_count != 0 {
let Some(types) = read_slice_mut(data, &mut offset, upvalue_count as usize) else {
return false;
};
for ty in types {
let index = usize::from(ty.wrapping_sub(BytecodeTypeTag::TaggedUserdataBase as u8));
if index < count {
*ty = remapping[index];
}
}
}
for _ in 0..local_count {
let Some(ty) = data.get_mut(offset) else {
return false;
};
let index = usize::from((*ty).wrapping_sub(BytecodeTypeTag::TaggedUserdataBase as u8));
if index < count {
*ty = remapping[index];
}
let Some(next_offset) = offset.checked_add(2) else {
return false;
};
if next_offset > data.len() {
return false;
}
offset = next_offset;
if read_var_int(data, &mut offset).is_none() || read_var_int(data, &mut offset).is_none() {
return false;
}
}
offset == size
}
fn load_safe(thread: &Thread, context: &mut LoadContext<'_>) -> VmResult<i32> {
let data = context.data;
let mut offset = 0usize;
macro_rules! malformed {
() => {
return malformed_bytecode(thread, context.chunk_name)
};
}
macro_rules! read_value {
($ty:ty) => {
match read::<$ty>(data, &mut offset) {
Some(value) => value,
None => malformed!(),
}
};
}
macro_rules! read_var_u32 {
() => {
match read_var_int(data, &mut offset) {
Some(value) => value,
None => malformed!(),
}
};
}
macro_rules! read_var_u64 {
() => {
match read_var_int64(data, &mut offset) {
Some(value) => value,
None => malformed!(),
}
};
}
macro_rules! read_bytes {
($len:expr) => {
match read_slice(data, &mut offset, $len) {
Some(bytes) => bytes,
None => malformed!(),
}
};
}
macro_rules! read_string_value {
() => {
match read_string(&context.strings, data, &mut offset) {
Some(value) => value,
None => malformed!(),
}
};
}
let version = read_value!(u8);
if version == 0 {
let mut chunk_buffer = [0u8; LUA_ID_SIZE];
let chunk_id = chunk_id(&mut chunk_buffer, context.chunk_name);
let remaining = &data[offset..];
unsafe {
crate::push_fstring!(
thread,
"%s%.*s",
&chunk_id,
remaining.len() as i32,
remaining
)?
};
return Ok(1);
}
if !(BYTECODE_VERSION_MIN..=BYTECODE_VERSION_MAX).contains(&version)
&& version != BYTECODE_VERSION_CLASSES
{
let mut chunk_buffer = [0u8; LUA_ID_SIZE];
let chunk_id = chunk_id(&mut chunk_buffer, context.chunk_name);
unsafe {
crate::push_fstring!(
thread,
"%s: bytecode version mismatch (expected [%d..%d], got %d)",
&chunk_id,
BYTECODE_VERSION_MIN as i32,
BYTECODE_VERSION_MAX as i32,
version as i32
)?
};
return Ok(1);
}
let mut type_version = 0u8;
if version >= 4 {
type_version = read_value!(u8);
if !(BYTECODE_TYPE_VERSION_MIN..=BYTECODE_TYPE_VERSION_MAX).contains(&type_version) {
let mut chunk_buffer = [0u8; LUA_ID_SIZE];
let chunk_id = chunk_id(&mut chunk_buffer, context.chunk_name);
unsafe {
crate::push_fstring!(
thread,
"%s: bytecode type version mismatch (expected [%d..%d], got %d)",
&chunk_id,
BYTECODE_TYPE_VERSION_MIN as i32,
BYTECODE_TYPE_VERSION_MAX as i32,
type_version as i32
)?
};
return Ok(1);
}
}
let globals = unsafe { thread.globals() };
let env_table = if context.env == 0 {
globals
} else {
unsafe {
let env = thread.to_object(context.env);
debug_assert!(env.is_some());
let env = env.unwrap_unchecked();
debug_assert!(env.is_table());
env.table_value()
}
};
let source = unsafe { thread.intern_string(context.chunk_name.as_bstr())? };
let string_count = read_var_u32!() as usize;
unsafe { context.strings.allocate(thread, string_count)? };
for index in 0..string_count {
let len = read_var_u32!() as usize;
let bytes = read_bytes!(len);
let string = unsafe { thread.intern_string(bytes.as_bstr())? };
unsafe {
*context.strings.data.add(index) = string;
}
}
let mut userdata_remapping = [BytecodeTypeTag::Userdata as u8; USERDATA_TYPE_LIMIT];
if type_version == 3 {
let mut index = read_value!(u8);
while index != 0 {
let name = read_string_value!();
if usize::from(index - 1) < USERDATA_TYPE_LIMIT
&& let Some(callback) = unsafe { thread.global().execution_type_mapping() }
&& let Some(name) = name
{
userdata_remapping[usize::from(index - 1)] =
unsafe { callback(thread, name.as_bytes().as_bstr()) };
}
index = read_value!(u8);
}
}
let proto_count = read_var_u32!() as usize;
unsafe { context.protos.allocate(thread, proto_count)? };
for index in 0..proto_count {
let proto_size = if version >= 12 {
read_var_u32!() as usize
} else {
0
};
let proto_start_offset = offset;
let proto = unsafe { thread.new_proto()? };
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.source = source.as_ptr();
proto_ref.bytecode_id = index as i32;
let global_handle = thread.global();
let global = global_handle.as_ptr().as_mut().unwrap_unchecked();
proto_ref.fun_id = if global.last_proto_id == 0 {
0
} else {
let result = global.last_proto_id;
global.last_proto_id += 1;
result
};
proto_ref.max_stack_size = read_value!(u8);
proto_ref.num_params = read_value!(u8);
proto_ref.n_ups = read_value!(u8);
proto_ref.is_vararg = read_value!(u8);
}
if version >= 4 {
unsafe {
proto.as_ptr().as_mut().unwrap_unchecked().flags = read_value!(u8);
}
if type_version == 1 {
let type_size = read_var_u32!() as usize;
if type_size != 0 {
let types = read_bytes!(type_size);
if type_size
!= 2 + unsafe {
proto.as_ptr().as_ref().unwrap_unchecked().num_params as usize
}
|| types.first().copied() != Some(BytecodeTypeTag::Function as u8)
|| types.get(1).copied()
!= Some(unsafe {
proto.as_ptr().as_ref().unwrap_unchecked().num_params
})
{
malformed!();
}
let header_size = if type_size > 127 { 4 } else { 3 };
let total_size = header_size + type_size;
let allocated = unsafe {
thread.new_array::<u8>(
total_size,
proto.as_ptr().as_ref().unwrap_unchecked().memcat,
)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.type_info = allocated;
proto_ref.size_type_info = total_size as i32;
if header_size == 4 {
*allocated.add(0) = ((type_size & 127) as u8) | (1 << 7);
*allocated.add(1) = (type_size >> 7) as u8;
*allocated.add(2) = 0;
*allocated.add(3) = 0;
} else {
*allocated.add(0) = type_size as u8;
*allocated.add(1) = 0;
*allocated.add(2) = 0;
}
ptr::copy_nonoverlapping(
types.as_ptr(),
allocated.add(header_size),
type_size,
);
}
}
} else if type_version == 2 || type_version == 3 {
let type_size = read_var_u32!() as usize;
if type_size != 0 {
let types = read_bytes!(type_size);
let allocated = unsafe {
thread.new_array::<u8>(
type_size,
proto.as_ptr().as_ref().unwrap_unchecked().memcat,
)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.type_info = allocated;
proto_ref.size_type_info = type_size as i32;
ptr::copy_nonoverlapping(types.as_ptr(), allocated, type_size);
}
if type_version == 3 {
if unsafe {
remap_userdata_types(
proto.as_ptr().as_ref().unwrap_unchecked().type_info,
proto.as_ptr().as_ref().unwrap_unchecked().size_type_info as usize,
&userdata_remapping,
USERDATA_TYPE_LIMIT,
)
} {
} else {
malformed!();
}
}
}
}
}
let size_code = read_var_u32!() as usize;
if size_code != 0 {
let code = unsafe {
thread.new_array::<u32>(
size_code,
proto.as_ptr().as_ref().unwrap_unchecked().memcat,
)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.code = code;
proto_ref.size_code = size_code as i32;
for word in 0..size_code {
*proto_ref.code.add(word) = read_value!(u32);
}
proto_ref.code_entry = proto_ref.code;
}
}
let size_k = read_var_u32!() as usize;
if size_k != 0 {
let constants = unsafe {
thread.new_array::<RawTValue>(
size_k,
proto.as_ptr().as_ref().unwrap_unchecked().memcat,
)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.k = constants;
proto_ref.size_k = size_k as i32;
let constants = TValueCursor::from_ptr(proto_ref.k);
for constant_index in 0..size_k {
constants.add(constant_index).value_unchecked().set_nil();
}
}
}
for constant_index in
0..unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_k as usize }
{
let constant = unsafe { proto.constant(constant_index) };
let constant_count =
unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_k as usize };
match read_value!(u8) {
tag if tag == BytecodeConstantTag::Nil as u8 => {}
tag if tag == BytecodeConstantTag::Boolean as u8 => {
constant.set_boolean(i32::from(read_value!(u8)));
}
tag if tag == BytecodeConstantTag::Number as u8 => {
constant.set_number(read_value!(f64));
}
tag if tag == BytecodeConstantTag::Vector as u8 => {
let x = read_value!(f32);
let y = read_value!(f32);
let z = read_value!(f32);
let w = read_value!(f32);
#[cfg(not(feature = "vector4"))]
{
let _ = w;
constant.set_vector([x, y, z]);
}
#[cfg(feature = "vector4")]
constant.set_vector([x, y, z, w]);
}
tag if tag == BytecodeConstantTag::VectorDouble as u8 => {
let x = read_value!(f64) as f32;
let y = read_value!(f64) as f32;
let z = read_value!(f64) as f32;
let w = read_value!(f64) as f32;
#[cfg(not(feature = "vector4"))]
{
let _ = w;
constant.set_vector([x, y, z]);
}
#[cfg(feature = "vector4")]
constant.set_vector([x, y, z, w]);
}
tag if tag == BytecodeConstantTag::String as u8 => {
let Some(value) = read_string_value!() else {
malformed!();
};
constant.set_string_value(value);
}
tag if tag == BytecodeConstantTag::Import as u8 => {
let id = read_value!(u32);
if !import_id_is_valid(id, constant_count) {
malformed!();
}
resolve_import_safe(thread, unsafe { proto.constants() }, env_table, id)?;
unsafe {
let top = thread.stack_top();
constant.set_obj(top.sub(1).value_unchecked());
thread.set_stack_top(top.sub(1));
}
}
tag if tag == BytecodeConstantTag::Table as u8 => {
let keys = read_var_u32!() as usize;
let table = unsafe { thread.new_table_internal(0, keys as i32)? };
for _ in 0..keys {
let key = read_var_u32!() as usize;
if key >= constant_count {
malformed!();
}
let slot = unsafe { thread.set(table, proto.constant(key))? };
slot.set_number(0.0);
}
constant.set_table_value(table);
}
tag if tag == BytecodeConstantTag::TableWithConstants as u8 => {
let keys = read_var_u32!() as usize;
let table = unsafe { thread.new_table_internal(0, keys as i32)? };
let mut nil_keys = TempBuffer::<i32>::new();
unsafe { nil_keys.allocate(thread, keys)? };
let mut nil_keys_size = 0usize;
for _ in 0..keys {
let key = read_var_u32!() as usize;
if key >= constant_count {
malformed!();
}
let slot = unsafe { thread.set(table, proto.constant(key))? };
let constant_index = read_value!(i32);
if constant_index >= 0 {
if constant_index as usize >= constant_count {
malformed!();
}
let constant_value = unsafe { proto.constant(constant_index as usize) };
if constant_value.is_nil() {
unsafe {
*nil_keys.data.add(nil_keys_size) = key as i32;
}
nil_keys_size += 1;
} else {
slot.set_obj(constant_value);
if constant_value.is_collectable() {
let table_object: GcObject = table.into();
let child = constant_value.gc_value();
if unsafe { table_object.is_black() }
&& unsafe { child.is_white() }
{
unsafe { thread.barrier_table(table, child) };
}
}
continue;
}
}
slot.set_number(0.0);
}
for nil_index in 0..nil_keys_size {
let key = unsafe { *nil_keys.data.add(nil_index) } as usize;
let slot = unsafe { thread.set(table, proto.constant(key))? };
slot.set_nil();
}
constant.set_table_value(table);
}
tag if tag == BytecodeConstantTag::Closure as u8 => {
let function_id = read_var_u32!() as usize;
if function_id >= index {
malformed!();
}
let child = unsafe { *context.protos.data.add(function_id) };
let closure = unsafe {
thread.new_lua_closure(
i32::from(child.as_ptr().as_ref().unwrap_unchecked().n_ups),
Some(env_table),
child,
)?
};
unsafe {
let closure_ref = closure.as_ptr().as_mut().unwrap_unchecked();
closure_ref.preload = u8::from(closure_ref.n_upvalues > 0);
constant.set_closure_value(closure);
}
}
tag if tag == BytecodeConstantTag::ClassShape as u8 => {
let class_name_id = read_var_u32!() as usize;
if class_name_id >= constant_count {
malformed!();
}
let class_name = unsafe { proto.constant(class_name_id) };
if !class_name.is_string() {
malformed!();
}
let number_of_instance_members = read_var_u32!();
let number_of_static_members = read_var_u32!();
let Some(number_of_members) =
number_of_instance_members.checked_add(number_of_static_members)
else {
malformed!();
};
let Ok(number_of_members) = usize::try_from(number_of_members) else {
malformed!();
};
let Ok(table_capacity) = i32::try_from(number_of_members) else {
malformed!();
};
let mut members_end = offset;
for _ in 0..number_of_members {
let Some(member_id) = read_var_int(data, &mut members_end) else {
malformed!();
};
let member_id = member_id as usize;
if member_id >= constant_count {
malformed!();
}
let member_name = unsafe { proto.constant(member_id) };
if !member_name.is_string() {
malformed!();
}
}
let offset_to_member = unsafe {
thread.new_array::<TString>(
number_of_members,
thread.as_ptr().as_ref().unwrap_unchecked().active_memcat,
)?
};
let members_to_offset =
unsafe { thread.new_table_internal(0, table_capacity)? };
for member_index in 0..number_of_members {
let member_id = read_var_u32!() as usize;
if member_id >= constant_count {
malformed!();
}
let member_name = unsafe { proto.constant(member_id) };
if !member_name.is_string() {
malformed!();
}
unsafe {
*offset_to_member.add(member_index) = member_name.string_value();
}
let node_cursor = unsafe {
thread.set_str(members_to_offset, member_name.string_value())?
};
unsafe {
node_cursor
.node_unchecked()
.value_unchecked()
.set_number(member_index as f64);
}
}
debug_assert_eq!(offset, members_end);
unsafe {
members_to_offset
.as_ptr()
.as_mut()
.unwrap_unchecked()
.readonly = 1;
}
let class = unsafe {
thread.new_class(
class_name.string_value(),
members_to_offset,
offset_to_member,
number_of_instance_members,
number_of_static_members,
)?
};
constant.set_class_value(class);
}
tag if tag == BytecodeConstantTag::Integer as u8 => {
let is_negative = read_value!(u8) != 0;
let magnitude = read_var_u64!();
constant.set_integer(if is_negative {
(!magnitude).wrapping_add(1) as i64
} else {
magnitude as i64
});
}
other => return malformed_constant_kind(thread, context.chunk_name, other),
}
}
let userdata_direct_access_6 = flags::LuauUdataDirectAccess6.get();
let code = unsafe { proto.as_ptr().as_ref().unwrap_unchecked().code };
let size_code = unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_code as usize };
let mut instruction_index = 0;
while instruction_index < size_code {
let instruction = unsafe { code.add(instruction_index) };
let Ok(opcode) = (unsafe { Instruction::new(*instruction) }).try_opcode() else {
malformed!();
};
if userdata_direct_access_6 {
let target_op = match opcode {
Opcode::GetTableKs => Some(Opcode::GetUDataKs),
Opcode::SetTableKs => Some(Opcode::SetUDataKs),
Opcode::NameCall => Some(Opcode::NameCallUData),
_ => None,
};
if let Some(target_op) = target_op {
if size_code - instruction_index < 2 {
malformed!();
}
let aux = unsafe { *instruction.add(1) } as usize;
if aux >= unsafe { proto.as_ptr().as_ref().unwrap_unchecked().size_k as usize }
{
malformed!();
}
if aux < 0x10000 {
let constant = unsafe { proto.constant(aux) };
if !constant.is_string() {
malformed!();
}
let string = constant.string_value();
unsafe { thread.update_atom(string) };
if unsafe { string.as_ptr().as_ref().unwrap_unchecked().atom } >= 0 {
unsafe {
*instruction = (*instruction & 0xffff_ff00) | target_op as u32;
}
}
}
}
}
let step = opcode.length();
if step == 0 || step > size_code - instruction_index {
malformed!();
}
instruction_index += step;
}
let size_p = read_var_u32!() as usize;
if size_p != 0 {
let protos = unsafe {
thread
.new_array::<Proto>(size_p, proto.as_ptr().as_ref().unwrap_unchecked().memcat)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.p = protos.cast::<*mut RawProto>();
proto_ref.size_p = size_p as i32;
for child_index in 0..size_p {
let function_id = read_var_u32!() as usize;
if function_id >= index {
malformed!();
}
*protos.add(child_index) = *context.protos.data.add(function_id);
*proto_ref.p.add(child_index) = (*protos.add(child_index)).as_ptr();
}
}
}
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.line_defined = read_var_u32!() as i32;
proto_ref.debug_name =
read_string_value!().map_or(ptr::null_mut(), |string| string.as_ptr());
}
if read_value!(u8) != 0 {
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
let line_gap_log2 = read_value!(u8);
let Some((intervals, abs_offset, size_line_info)) =
line_info_layout(proto_ref.size_code, line_gap_log2)
else {
malformed!();
};
let bytes = thread.new_array::<u8>(size_line_info, proto_ref.memcat)?;
proto_ref.line_gap_log2 = i32::from(line_gap_log2);
proto_ref.line_info = bytes;
proto_ref.size_line_info = size_line_info as i32;
proto_ref.abs_line_info = bytes.add(abs_offset).cast::<i32>();
let mut last_offset = 0u8;
for line_index in 0..proto_ref.size_code as usize {
last_offset = last_offset.wrapping_add(read_value!(u8));
*proto_ref.line_info.add(line_index) = last_offset;
}
let mut last_line = 0i32;
for interval_index in 0..intervals {
let Some(line) = last_line.checked_add(read_value!(i32)) else {
malformed!();
};
last_line = line;
*proto_ref.abs_line_info.add(interval_index) = last_line;
}
}
}
if read_value!(u8) != 0 {
let size_locals = read_var_u32!() as usize;
if size_locals != 0 {
let locals = unsafe {
thread.new_array::<RawLocVar>(
size_locals,
proto.as_ptr().as_ref().unwrap_unchecked().memcat,
)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.loc_vars = locals;
proto_ref.size_loc_vars = size_locals as i32;
for local_index in 0..size_locals {
let local = &mut *proto_ref.loc_vars.add(local_index);
local.var_name =
read_string_value!().map_or(ptr::null_mut(), |string| string.as_ptr());
local.start_pc = read_var_u32!() as i32;
local.end_pc = read_var_u32!() as i32;
local.reg = read_value!(u8);
}
}
}
let size_upvalues = read_var_u32!() as usize;
if size_upvalues as u8 != unsafe { proto.as_ptr().as_ref().unwrap_unchecked().n_ups } {
malformed!();
}
if size_upvalues != 0 {
let upvalues = unsafe {
thread.new_array::<*mut RawTString>(
size_upvalues,
proto.as_ptr().as_ref().unwrap_unchecked().memcat,
)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.upvalues = upvalues;
proto_ref.size_upvalues = size_upvalues as i32;
for upvalue_index in 0..size_upvalues {
*proto_ref.upvalues.add(upvalue_index) =
read_string_value!().map_or(ptr::null_mut(), |string| string.as_ptr());
}
}
}
}
if version >= 11 {
let size = read_var_u32!() as usize;
unsafe {
proto.as_ptr().as_mut().unwrap_unchecked().feedback_vec_size = size as u32;
}
if size != 0 {
let feedback = unsafe {
thread.new_array::<FeedbackVectorSlot>(
size,
proto.as_ptr().as_ref().unwrap_unchecked().memcat,
)?
};
unsafe {
let proto_ref = proto.as_ptr().as_mut().unwrap_unchecked();
proto_ref.feedback_vec = feedback;
for slot_index in 0..size {
let slot_type = read_value!(u8);
if slot_type != FEEDBACK_TYPE_CALLTARGET {
malformed!();
}
*proto_ref.feedback_vec.add(slot_index) = FeedbackVectorSlot {
kind: i32::from(slot_type),
data: FeedbackVectorSlotData {
call_target: FeedbackVectorSlotCallTarget {
pc: read_var_u32!(),
proto: 0,
hits: 0,
},
},
};
}
}
}
}
if version >= 12
&& unsafe { proto.as_ptr().as_ref().unwrap_unchecked().flags } & PROTO_FLAG_INLINABLE
!= 0
{
unsafe {
proto.as_ptr().as_mut().unwrap_unchecked().cost = read_var_u64!();
}
}
if version >= 12 {
let Some(proto_end_offset) = proto_start_offset.checked_add(proto_size) else {
malformed!();
};
if proto_end_offset > data.len() || proto_end_offset < offset {
malformed!();
}
offset = proto_end_offset;
}
unsafe {
*context.protos.data.add(index) = proto;
}
}
let main_id = read_var_u32!() as usize;
if main_id >= context.protos.count {
malformed!();
}
let main_proto = unsafe { *context.protos.data.add(main_id) };
unsafe { thread.thread_barrier() };
let closure = unsafe { thread.new_lua_closure(0, Some(env_table), main_proto)? };
let top = unsafe { thread.stack_top() };
unsafe {
top.value_unchecked().set_closure_value(closure);
thread.set_stack_top(top.add(1));
}
Ok(0)
}
unsafe fn load_callback(thread: &Thread, context: &mut LoadContext<'_>) -> VmResult {
if load_safe(thread, context)? != 0 {
return Err(VmError::Syntax.into());
}
Ok(())
}
impl Thread {
pub unsafe fn load(
&self,
chunk_name: impl AsRef<[u8]>,
data: impl AsRef<[u8]>,
env: i32,
) -> VmResult {
unsafe { self.load_bytecode(chunk_name.as_ref(), data.as_ref(), env) }
}
pub(crate) unsafe fn load_bytecode(
&self,
chunk_name: &[u8],
data: &[u8],
env: i32,
) -> VmResult {
unsafe {
let _pause_gc = {
self.check_gc()?;
ScopedGcThreshold::new(self.global(), usize::MAX)
};
let mut context = LoadContext {
strings: TempBuffer::new(),
protos: TempBuffer::new(),
chunk_name,
data,
env,
};
match self.raw_run_protected(load_callback, &mut context) {
Ok(()) => Ok(()),
Err(error) => {
if let VmExit::Error(VmError::Memory) = error {
let top = self.stack_top();
top.value_unchecked().set_string_value(
self.intern_string(LUA_MEMERRMSG.as_bstr()).expect(
"memory error message is fixed during state initialization",
),
);
self.set_stack_top(top.add(1));
}
Err(error)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::{line_info_layout, remap_userdata_types};
#[test]
fn line_info_layout_rejects_invalid_code_sizes_and_shifts() {
assert_eq!(line_info_layout(2, 0), Some((2, 4, 12)));
assert_eq!(line_info_layout(0, 0), None);
assert_eq!(line_info_layout(2, 32), None);
}
#[test]
fn userdata_type_remapping_rejects_trailing_data() {
let mut data = [0, 0, 0, 0];
assert!(!remap_userdata_types(data.as_mut_ptr(), data.len(), &[], 0));
}
}