use super::{Chunk, LocVar, LuaValue, UpvalueDesc};
use crate::Instruction;
use crate::gc::{GcProto, ObjectAllocator};
use crate::lua_vm::LuaVM;
use crate::lua_vm::lua_limits::LUAI_MAXSHORTLEN;
use std::collections::HashMap;
use std::io::{Cursor, Read};
fn detached_proto(chunk: Chunk) -> crate::ProtoPtr {
let size = std::mem::size_of::<GcProto>() as u32 + chunk.proto_data_size;
let boxed = Box::new(GcProto::new(chunk, 0, size));
crate::ProtoPtr::new(Box::leak(boxed) as *const GcProto)
}
const LUARS_MAGIC: &[u8] = b"\x1bLuaRS";
const LUARS_VERSION: u8 = 1;
pub fn serialize_chunk_with_pool(
chunk: &Chunk,
strip: bool,
pool: &ObjectAllocator,
) -> Result<Vec<u8>, String> {
let mut buf = Vec::new();
buf.extend_from_slice(LUARS_MAGIC);
buf.push(LUARS_VERSION);
buf.push(if strip { 1 } else { 0 });
let mut string_table = HashMap::new();
write_chunk_with_dedup(&mut buf, chunk, strip, pool, &mut string_table)?;
Ok(buf)
}
pub fn serialize_chunk(chunk: &Chunk, strip: bool) -> Result<Vec<u8>, String> {
let mut buf = Vec::new();
buf.extend_from_slice(LUARS_MAGIC);
buf.push(LUARS_VERSION);
buf.push(if strip { 1 } else { 0 });
let mut string_table = HashMap::new();
write_chunk_no_pool_with_dedup(&mut buf, chunk, strip, &mut string_table)?;
Ok(buf)
}
pub fn deserialize_chunk(data: &[u8]) -> Result<Chunk, String> {
let mut cursor = Cursor::new(data);
let mut magic = [0u8; 6];
cursor
.read_exact(&mut magic)
.map_err(|e| format!("failed to read magic: {}", e))?;
if magic != LUARS_MAGIC {
return Err("not a lua-rs bytecode file".to_string());
}
let mut version = [0u8; 1];
cursor
.read_exact(&mut version)
.map_err(|e| format!("failed to read version: {}", e))?;
if version[0] != LUARS_VERSION {
return Err(format!("unsupported bytecode version: {}", version[0]));
}
let mut stripped = [0u8; 1];
cursor
.read_exact(&mut stripped)
.map_err(|_| "failed to read strip flag")?;
let mut string_table = Vec::new();
read_chunk_with_dedup(&mut cursor, &mut string_table)
}
pub fn deserialize_chunk_with_strings_vm(data: &[u8], vm: &mut LuaVM) -> Result<Chunk, String> {
let mut cursor = Cursor::new(data);
let mut magic = [0u8; 6];
cursor
.read_exact(&mut magic)
.map_err(|e| format!("failed to read magic: {}", e))?;
if magic != LUARS_MAGIC {
return Err("not a lua-rs bytecode file".to_string());
}
let mut version = [0u8; 1];
cursor
.read_exact(&mut version)
.map_err(|e| format!("failed to read version: {}", e))?;
if version[0] != LUARS_VERSION {
return Err(format!("unsupported bytecode version: {}", version[0]));
}
let mut stripped = [0u8; 1];
cursor
.read_exact(&mut stripped)
.map_err(|_| "failed to read strip flag")?;
let mut string_table = Vec::new();
let chunk = read_chunk_with_vm_dedup(&mut cursor, vm, &mut string_table)?;
Ok(chunk)
}
pub fn deserialize_chunk_with_strings(
data: &[u8],
) -> Result<(Chunk, Vec<(usize, String)>), String> {
let mut cursor = Cursor::new(data);
let mut magic = [0u8; 6];
cursor
.read_exact(&mut magic)
.map_err(|e| format!("failed to read magic: {}", e))?;
if magic != LUARS_MAGIC {
return Err("not a lua-rs bytecode file".to_string());
}
let mut version = [0u8; 1];
cursor
.read_exact(&mut version)
.map_err(|e| format!("failed to read version: {}", e))?;
if version[0] != LUARS_VERSION {
return Err(format!("unsupported bytecode version: {}", version[0]));
}
let mut stripped = [0u8; 1];
cursor
.read_exact(&mut stripped)
.map_err(|_| "failed to read strip flag")?;
let mut strings = Vec::new();
let chunk = read_chunk_with_strings(&mut cursor, &mut strings)?;
Ok((chunk, strings))
}
#[allow(dead_code)]
fn write_chunk(
buf: &mut Vec<u8>,
chunk: &Chunk,
strip: bool,
pool: &ObjectAllocator,
) -> Result<(), String> {
write_u32(buf, chunk.code.len() as u32);
for &instr in &chunk.code {
write_u32(buf, instr.as_u32());
}
write_u32(buf, chunk.constants.len() as u32);
for constant in &chunk.constants {
write_constant_with_pool(buf, constant)?;
}
write_u32(buf, chunk.upvalue_count as u32);
write_u32(buf, chunk.param_count as u32);
buf.push(if chunk.is_vararg { 1 } else { 0 });
buf.push(if chunk.needs_vararg_table { 1 } else { 0 });
write_u32(buf, chunk.max_stack_size as u32);
write_u32(buf, chunk.linedefined as u32);
write_u32(buf, chunk.lastlinedefined as u32);
write_u32(buf, chunk.upvalue_descs.len() as u32);
for desc in &chunk.upvalue_descs {
if strip {
write_string(buf, ""); } else {
write_string(buf, &desc.name);
}
buf.push(if desc.is_local { 1 } else { 0 });
write_u32(buf, desc.index);
}
write_u32(buf, chunk.child_protos.len() as u32);
for child in &chunk.child_protos {
write_chunk(buf, &child.as_ref().data, strip, pool)?;
}
if strip {
write_u32(buf, 0); write_u32(buf, 0); write_u32(buf, 0); } else {
if let Some(ref name) = chunk.source_name {
write_string(buf, name);
} else {
write_u32(buf, 0);
}
write_u32(buf, chunk.locals.len() as u32);
for local in &chunk.locals {
write_string(buf, &local.name);
write_u32(buf, local.startpc);
write_u32(buf, local.endpc);
}
write_u32(buf, chunk.line_info.len() as u32);
for &line in &chunk.line_info {
write_u32(buf, line);
}
}
Ok(())
}
fn write_chunk_with_dedup(
buf: &mut Vec<u8>,
chunk: &Chunk,
strip: bool,
pool: &ObjectAllocator,
string_table: &mut HashMap<String, u32>,
) -> Result<(), String> {
write_u32(buf, chunk.code.len() as u32);
for &instr in &chunk.code {
write_u32(buf, instr.as_u32());
}
write_u32(buf, chunk.constants.len() as u32);
for constant in &chunk.constants {
write_constant_with_dedup(buf, constant, string_table)?;
}
write_u32(buf, chunk.upvalue_count as u32);
write_u32(buf, chunk.param_count as u32);
buf.push(if chunk.is_vararg { 1 } else { 0 });
buf.push(if chunk.needs_vararg_table { 1 } else { 0 });
write_u32(buf, chunk.max_stack_size as u32);
write_u32(buf, chunk.linedefined as u32);
write_u32(buf, chunk.lastlinedefined as u32);
write_u32(buf, chunk.upvalue_descs.len() as u32);
for desc in &chunk.upvalue_descs {
if strip {
write_string_with_dedup(buf, "", string_table)?; } else {
write_string_with_dedup(buf, &desc.name, string_table)?;
}
buf.push(if desc.is_local { 1 } else { 0 });
write_u32(buf, desc.index);
}
write_u32(buf, chunk.child_protos.len() as u32);
for child in &chunk.child_protos {
write_chunk_with_dedup(buf, &child.as_ref().data, strip, pool, string_table)?;
}
if strip {
write_u32(buf, 0); write_u32(buf, 0); write_u32(buf, 0); write_u32(buf, 0); } else {
if let Some(ref name) = chunk.source_name {
write_string_with_dedup(buf, name, string_table)?; } else {
write_u32(buf, 0); write_u32(buf, 0); }
write_u32(buf, chunk.locals.len() as u32);
for local in &chunk.locals {
write_string_with_dedup(buf, &local.name, string_table)?; write_u32(buf, local.startpc);
write_u32(buf, local.endpc);
}
write_u32(buf, chunk.line_info.len() as u32);
for &line in &chunk.line_info {
write_u32(buf, line);
}
}
Ok(())
}
fn write_chunk_no_pool_with_dedup(
buf: &mut Vec<u8>,
chunk: &Chunk,
strip: bool,
string_table: &mut HashMap<String, u32>,
) -> Result<(), String> {
write_u32(buf, chunk.code.len() as u32);
for &instr in &chunk.code {
write_u32(buf, instr.as_u32());
}
write_u32(buf, chunk.constants.len() as u32);
for constant in &chunk.constants {
write_constant_no_pool(buf, constant)?;
}
write_u32(buf, chunk.upvalue_count as u32);
write_u32(buf, chunk.param_count as u32);
buf.push(if chunk.is_vararg { 1 } else { 0 });
buf.push(if chunk.needs_vararg_table { 1 } else { 0 });
write_u32(buf, chunk.max_stack_size as u32);
write_u32(buf, chunk.linedefined as u32);
write_u32(buf, chunk.lastlinedefined as u32);
write_u32(buf, chunk.upvalue_descs.len() as u32);
for desc in &chunk.upvalue_descs {
if strip {
write_string_with_dedup(buf, "", string_table)?; } else {
write_string_with_dedup(buf, &desc.name, string_table)?;
}
buf.push(if desc.is_local { 1 } else { 0 });
write_u32(buf, desc.index);
}
write_u32(buf, chunk.child_protos.len() as u32);
for child in &chunk.child_protos {
write_chunk_no_pool_with_dedup(buf, &child.as_ref().data, strip, string_table)?;
}
if strip {
write_u32(buf, 0);
write_u32(buf, 0);
write_u32(buf, 0);
write_u32(buf, 0);
} else {
if let Some(ref name) = chunk.source_name {
write_string_with_dedup(buf, name, string_table)?;
} else {
write_u32(buf, 0);
write_u32(buf, 0);
}
write_u32(buf, chunk.locals.len() as u32);
for local in &chunk.locals {
write_string_with_dedup(buf, &local.name, string_table)?;
write_u32(buf, local.startpc);
write_u32(buf, local.endpc);
}
write_u32(buf, chunk.line_info.len() as u32);
for &line in &chunk.line_info {
write_u32(buf, line);
}
}
Ok(())
}
#[allow(dead_code)]
fn write_chunk_no_pool(buf: &mut Vec<u8>, chunk: &Chunk, strip: bool) -> Result<(), String> {
write_u32(buf, chunk.code.len() as u32);
for &instr in &chunk.code {
write_u32(buf, instr.as_u32());
}
write_u32(buf, chunk.constants.len() as u32);
for constant in &chunk.constants {
write_constant_no_pool(buf, constant)?;
}
write_u32(buf, chunk.upvalue_count as u32);
write_u32(buf, chunk.param_count as u32);
buf.push(if chunk.is_vararg { 1 } else { 0 });
buf.push(if chunk.needs_vararg_table { 1 } else { 0 });
write_u32(buf, chunk.max_stack_size as u32);
write_u32(buf, chunk.linedefined as u32);
write_u32(buf, chunk.lastlinedefined as u32);
write_u32(buf, chunk.upvalue_descs.len() as u32);
for desc in &chunk.upvalue_descs {
if strip {
write_string(buf, ""); } else {
write_string(buf, &desc.name);
}
buf.push(if desc.is_local { 1 } else { 0 });
write_u32(buf, desc.index);
}
write_u32(buf, chunk.child_protos.len() as u32);
for child in &chunk.child_protos {
write_chunk_no_pool(buf, &child.as_ref().data, strip)?;
}
if strip {
write_u32(buf, 0);
write_u32(buf, 0);
write_u32(buf, 0);
} else {
if let Some(ref name) = chunk.source_name {
write_string(buf, name);
} else {
write_u32(buf, 0);
}
write_u32(buf, chunk.locals.len() as u32);
for local in &chunk.locals {
write_string(buf, &local.name);
write_u32(buf, local.startpc);
write_u32(buf, local.endpc);
}
write_u32(buf, chunk.line_info.len() as u32);
for &line in &chunk.line_info {
write_u32(buf, line);
}
}
Ok(())
}
#[allow(dead_code)]
fn read_chunk(cursor: &mut Cursor<&[u8]>) -> Result<Chunk, String> {
let code_len = read_u32(cursor)? as usize;
let mut code = Vec::with_capacity(code_len);
for _ in 0..code_len {
code.push(Instruction::from_u32(read_u32(cursor)?));
}
let const_len = read_u32(cursor)? as usize;
let mut constants = Vec::with_capacity(const_len);
for _ in 0..const_len {
constants.push(read_constant(cursor)?);
}
let upvalue_count = read_u32(cursor)? as usize;
let param_count = read_u32(cursor)? as usize;
let is_vararg = read_u8(cursor)? != 0;
let needs_vararg_table = read_u8(cursor)? != 0;
let max_stack_size = read_u32(cursor)? as usize;
let linedefined = read_u32(cursor)? as usize;
let lastlinedefined = read_u32(cursor)? as usize;
let desc_len = read_u32(cursor)? as usize;
let mut upvalue_descs = Vec::with_capacity(desc_len);
for _ in 0..desc_len {
let name = read_string(cursor)?;
let is_local = read_u8(cursor)? != 0;
let index = read_u32(cursor)?;
upvalue_descs.push(UpvalueDesc {
name,
is_local,
index,
});
}
let child_len = read_u32(cursor)? as usize;
let mut child_protos = Vec::with_capacity(child_len);
for _ in 0..child_len {
child_protos.push(detached_proto(read_chunk(cursor)?));
}
let source_name = read_optional_string(cursor)?;
let locals_len = read_u32(cursor)? as usize;
let mut locals = Vec::with_capacity(locals_len);
for _ in 0..locals_len {
let name = read_string(cursor)?;
let startpc = read_u32(cursor)?;
let endpc = read_u32(cursor)?;
locals.push(LocVar {
name,
startpc,
endpc,
});
}
let line_len = read_u32(cursor)? as usize;
let mut line_info = Vec::with_capacity(line_len);
for _ in 0..line_len {
line_info.push(read_u32(cursor)?);
}
let mut chunk = Chunk {
code,
constants,
locals,
upvalue_count,
param_count,
is_vararg,
needs_vararg_table,
use_hidden_vararg: false,
max_stack_size,
child_protos,
upvalue_descs,
source_name,
line_info,
linedefined,
lastlinedefined,
proto_data_size: 0,
};
chunk.compute_proto_data_size();
Ok(chunk)
}
fn read_chunk_with_dedup(
cursor: &mut Cursor<&[u8]>,
string_table: &mut Vec<String>,
) -> Result<Chunk, String> {
let code_len = read_u32(cursor)? as usize;
let mut code = Vec::with_capacity(code_len);
for _ in 0..code_len {
code.push(Instruction::from_u32(read_u32(cursor)?));
}
let const_len = read_u32(cursor)? as usize;
let mut constants = Vec::with_capacity(const_len);
for _ in 0..const_len {
constants.push(read_constant_with_dedup(cursor, string_table)?);
}
let upvalue_count = read_u32(cursor)? as usize;
let param_count = read_u32(cursor)? as usize;
let is_vararg = read_u8(cursor)? != 0;
let needs_vararg_table = read_u8(cursor)? != 0;
let max_stack_size = read_u32(cursor)? as usize;
let linedefined = read_u32(cursor)? as usize;
let lastlinedefined = read_u32(cursor)? as usize;
let desc_len = read_u32(cursor)? as usize;
let mut upvalue_descs = Vec::with_capacity(desc_len);
for _ in 0..desc_len {
let name = read_string_with_dedup(cursor, string_table)?;
let is_local = read_u8(cursor)? != 0;
let index = read_u32(cursor)?;
upvalue_descs.push(UpvalueDesc {
name,
is_local,
index,
});
}
let child_len = read_u32(cursor)? as usize;
let mut child_protos = Vec::with_capacity(child_len);
for _ in 0..child_len {
child_protos.push(detached_proto(read_chunk_with_dedup(cursor, string_table)?));
}
let source_name = read_optional_string_with_dedup(cursor, string_table)?;
let locals_len = read_u32(cursor)? as usize;
let mut locals = Vec::with_capacity(locals_len);
for _ in 0..locals_len {
let name = read_string_with_dedup(cursor, string_table)?;
let startpc = read_u32(cursor)?;
let endpc = read_u32(cursor)?;
locals.push(LocVar {
name,
startpc,
endpc,
});
}
let line_len = read_u32(cursor)? as usize;
let mut line_info = Vec::with_capacity(line_len);
for _ in 0..line_len {
line_info.push(read_u32(cursor)?);
}
let mut chunk = Chunk {
code,
constants,
locals,
upvalue_count,
param_count,
is_vararg,
needs_vararg_table,
use_hidden_vararg: false,
max_stack_size,
child_protos,
upvalue_descs,
source_name,
line_info,
linedefined,
lastlinedefined,
proto_data_size: 0,
};
chunk.compute_proto_data_size();
Ok(chunk)
}
const TAG_NIL: u8 = 0x00; const TAG_BOOL_FALSE: u8 = 0x01; const TAG_BOOL_TRUE: u8 = 0x11; const TAG_FLOAT: u8 = 0x03; const TAG_INTEGER: u8 = 0x13; const TAG_SHORT_STRING: u8 = 0x04; const TAG_LONG_STRING: u8 = 0x14; const TAG_BINARY: u8 = 0x24;
#[allow(dead_code)]
fn write_constant_with_pool(buf: &mut Vec<u8>, value: &LuaValue) -> Result<(), String> {
if value.is_nil() {
buf.push(TAG_NIL);
} else if let Some(b) = value.as_boolean() {
buf.push(if b { TAG_BOOL_TRUE } else { TAG_BOOL_FALSE });
} else if let Some(i) = value.as_integer_strict() {
buf.push(TAG_INTEGER);
write_i64(buf, i);
} else if let Some(f) = value.as_float() {
buf.push(TAG_FLOAT);
write_f64(buf, f);
} else if let Some(lua_string) = value.as_str() {
if lua_string.len() <= LUAI_MAXSHORTLEN {
buf.push(TAG_SHORT_STRING);
} else {
buf.push(TAG_LONG_STRING);
}
write_string(buf, lua_string);
} else {
buf.push(TAG_NIL);
}
Ok(())
}
fn write_constant_with_dedup(
buf: &mut Vec<u8>,
value: &LuaValue,
string_table: &mut HashMap<String, u32>,
) -> Result<(), String> {
if value.is_nil() {
buf.push(TAG_NIL);
} else if let Some(b) = value.as_boolean() {
buf.push(if b { TAG_BOOL_TRUE } else { TAG_BOOL_FALSE });
} else if let Some(i) = value.as_integer_strict() {
buf.push(TAG_INTEGER);
write_i64(buf, i);
} else if let Some(f) = value.as_float() {
buf.push(TAG_FLOAT);
write_f64(buf, f);
} else if let Some(bytes) = value.as_bytes() {
if value.as_str().is_none() {
buf.push(TAG_BINARY);
write_u32(buf, bytes.len() as u32);
buf.extend_from_slice(bytes);
} else if let Some(lua_string) = value.as_str() {
if bytes.len() <= LUAI_MAXSHORTLEN {
buf.push(TAG_SHORT_STRING);
} else {
buf.push(TAG_LONG_STRING);
}
write_string_with_dedup(buf, lua_string, string_table)?;
} else {
return Err(
"byte-string constant is missing both UTF-8 and raw-byte views".to_string(),
);
}
} else {
buf.push(TAG_NIL);
}
Ok(())
}
fn write_string_with_dedup(
buf: &mut Vec<u8>,
s: &str,
string_table: &mut HashMap<String, u32>,
) -> Result<(), String> {
if let Some(&index) = string_table.get(s) {
write_u32(buf, 0); write_u32(buf, index); } else {
let new_index = string_table.len() as u32 + 1; string_table.insert(s.to_string(), new_index);
if s.is_empty() {
write_u32(buf, 0); write_u32(buf, 0); } else {
write_string(buf, s);
}
}
Ok(())
}
fn write_constant_no_pool(buf: &mut Vec<u8>, value: &LuaValue) -> Result<(), String> {
if value.is_nil() {
buf.push(TAG_NIL);
} else if let Some(b) = value.as_boolean() {
buf.push(if b { TAG_BOOL_TRUE } else { TAG_BOOL_FALSE });
} else if let Some(i) = value.as_integer_strict() {
buf.push(TAG_INTEGER);
write_i64(buf, i);
} else if let Some(f) = value.as_float() {
buf.push(TAG_FLOAT);
write_f64(buf, f);
} else {
buf.push(TAG_NIL);
}
Ok(())
}
fn read_constant_with_strings(
cursor: &mut Cursor<&[u8]>,
const_index: usize,
strings: &mut Vec<(usize, String)>,
) -> Result<LuaValue, String> {
let tag = read_u8(cursor)?;
match tag {
TAG_NIL => Ok(LuaValue::nil()),
TAG_BOOL_FALSE => Ok(LuaValue::boolean(false)),
TAG_BOOL_TRUE => Ok(LuaValue::boolean(true)),
TAG_INTEGER => Ok(LuaValue::integer(read_i64(cursor)?)),
TAG_FLOAT => Ok(LuaValue::number(read_f64(cursor)?)),
TAG_SHORT_STRING | TAG_LONG_STRING => {
let s = read_string(cursor)?;
strings.push((const_index, s));
Ok(LuaValue::nil()) }
_ => Err(format!("unknown constant tag: {}", tag)),
}
}
#[allow(dead_code)]
fn read_constant(cursor: &mut Cursor<&[u8]>) -> Result<LuaValue, String> {
let tag = read_u8(cursor)?;
match tag {
TAG_NIL => Ok(LuaValue::nil()),
TAG_BOOL_FALSE => Ok(LuaValue::boolean(false)),
TAG_BOOL_TRUE => Ok(LuaValue::boolean(true)),
TAG_INTEGER => Ok(LuaValue::integer(read_i64(cursor)?)),
TAG_FLOAT => Ok(LuaValue::number(read_f64(cursor)?)),
TAG_SHORT_STRING | TAG_LONG_STRING => {
let len = read_u32(cursor)? as usize;
let mut buf = vec![0u8; len];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
Ok(LuaValue::nil())
}
_ => Err(format!("unknown constant tag: {}", tag)),
}
}
#[allow(dead_code)]
fn read_chunk_with_vm(cursor: &mut Cursor<&[u8]>, vm: &mut LuaVM) -> Result<Chunk, String> {
let code_len = read_u32(cursor)? as usize;
let mut code = Vec::with_capacity(code_len);
for _ in 0..code_len {
code.push(Instruction::from_u32(read_u32(cursor)?));
}
let const_len = read_u32(cursor)? as usize;
let mut constants = Vec::with_capacity(const_len);
for _ in 0..const_len {
constants.push(read_constant_with_vm(cursor, vm)?);
}
let upvalue_count = read_u32(cursor)? as usize;
let param_count = read_u32(cursor)? as usize;
let is_vararg = read_u8(cursor)? != 0;
let needs_vararg_table = read_u8(cursor)? != 0;
let max_stack_size = read_u32(cursor)? as usize;
let linedefined = read_u32(cursor)? as usize;
let lastlinedefined = read_u32(cursor)? as usize;
let desc_len = read_u32(cursor)? as usize;
let mut upvalue_descs = Vec::with_capacity(desc_len);
for _ in 0..desc_len {
let name = read_string(cursor)?;
let is_local = read_u8(cursor)? != 0;
let index = read_u32(cursor)?;
upvalue_descs.push(UpvalueDesc {
name,
is_local,
index,
});
}
let child_len = read_u32(cursor)? as usize;
let mut child_protos = Vec::with_capacity(child_len);
for _ in 0..child_len {
let child_chunk = read_chunk_with_vm(cursor, vm)?;
child_protos.push(vm.create_proto(child_chunk).map_err(|e| e.to_string())?);
}
let source_name = read_optional_string(cursor)?;
let locals_len = read_u32(cursor)? as usize;
let mut locals = Vec::with_capacity(locals_len);
for _ in 0..locals_len {
let name = read_string(cursor)?;
let startpc = read_u32(cursor)?;
let endpc = read_u32(cursor)?;
locals.push(LocVar {
name,
startpc,
endpc,
});
}
let line_len = read_u32(cursor)? as usize;
let mut line_info = Vec::with_capacity(line_len);
for _ in 0..line_len {
line_info.push(read_u32(cursor)?);
}
let mut chunk = Chunk {
code,
constants,
child_protos,
upvalue_count,
param_count,
is_vararg,
max_stack_size,
upvalue_descs,
source_name,
locals,
line_info,
needs_vararg_table,
use_hidden_vararg: false,
linedefined,
lastlinedefined,
proto_data_size: 0,
};
chunk.compute_proto_data_size();
Ok(chunk)
}
#[allow(dead_code)]
fn read_constant_with_vm(cursor: &mut Cursor<&[u8]>, vm: &mut LuaVM) -> Result<LuaValue, String> {
let tag = read_u8(cursor)?;
match tag {
TAG_NIL => Ok(LuaValue::nil()),
TAG_BOOL_FALSE => Ok(LuaValue::boolean(false)),
TAG_BOOL_TRUE => Ok(LuaValue::boolean(true)),
TAG_INTEGER => Ok(LuaValue::integer(read_i64(cursor)?)),
TAG_FLOAT => Ok(LuaValue::number(read_f64(cursor)?)),
TAG_SHORT_STRING | TAG_LONG_STRING => {
let s = read_string(cursor)?;
vm.create_bytes(s.as_bytes())
.map_err(|e| format!("failed to create string: {}", e))
}
_ => Err(format!("unknown constant tag: {}", tag)),
}
}
fn read_chunk_with_vm_dedup(
cursor: &mut Cursor<&[u8]>,
vm: &mut LuaVM,
string_table: &mut Vec<String>,
) -> Result<Chunk, String> {
let code_len = read_u32(cursor)? as usize;
let mut code = Vec::with_capacity(code_len);
for _ in 0..code_len {
code.push(Instruction::from_u32(read_u32(cursor)?));
}
let const_len = read_u32(cursor)? as usize;
let mut constants = Vec::with_capacity(const_len);
for _ in 0..const_len {
constants.push(read_constant_with_vm_dedup(cursor, vm, string_table)?);
}
let upvalue_count = read_u32(cursor)? as usize;
let param_count = read_u32(cursor)? as usize;
let is_vararg = read_u8(cursor)? != 0;
let needs_vararg_table = read_u8(cursor)? != 0;
let max_stack_size = read_u32(cursor)? as usize;
let linedefined = read_u32(cursor)? as usize;
let lastlinedefined = read_u32(cursor)? as usize;
let desc_len = read_u32(cursor)? as usize;
let mut upvalue_descs = Vec::with_capacity(desc_len);
for _ in 0..desc_len {
let name = read_string_with_dedup(cursor, string_table)?;
let is_local = read_u8(cursor)? != 0;
let index = read_u32(cursor)?;
upvalue_descs.push(UpvalueDesc {
name,
is_local,
index,
});
}
let child_len = read_u32(cursor)? as usize;
let mut child_protos = Vec::with_capacity(child_len);
for _ in 0..child_len {
let child_chunk = read_chunk_with_vm_dedup(cursor, vm, string_table)?;
child_protos.push(vm.create_proto(child_chunk).map_err(|e| e.to_string())?);
}
let source_name = read_optional_string_with_dedup(cursor, string_table)?;
let locals_len = read_u32(cursor)? as usize;
let mut locals = Vec::with_capacity(locals_len);
for _ in 0..locals_len {
let name = read_string_with_dedup(cursor, string_table)?;
let startpc = read_u32(cursor)?;
let endpc = read_u32(cursor)?;
locals.push(LocVar {
name,
startpc,
endpc,
});
}
let line_len = read_u32(cursor)? as usize;
let mut line_info = Vec::with_capacity(line_len);
for _ in 0..line_len {
line_info.push(read_u32(cursor)?);
}
let mut chunk = Chunk {
code,
constants,
child_protos,
upvalue_count,
param_count,
is_vararg,
max_stack_size,
upvalue_descs,
source_name,
locals,
line_info,
needs_vararg_table,
use_hidden_vararg: false,
linedefined,
lastlinedefined,
proto_data_size: 0,
};
chunk.compute_proto_data_size();
Ok(chunk)
}
fn read_constant_with_vm_dedup(
cursor: &mut Cursor<&[u8]>,
vm: &mut LuaVM,
string_table: &mut Vec<String>,
) -> Result<LuaValue, String> {
let tag = read_u8(cursor)?;
match tag {
TAG_NIL => Ok(LuaValue::nil()),
TAG_BOOL_FALSE => Ok(LuaValue::boolean(false)),
TAG_BOOL_TRUE => Ok(LuaValue::boolean(true)),
TAG_INTEGER => Ok(LuaValue::integer(read_i64(cursor)?)),
TAG_FLOAT => Ok(LuaValue::number(read_f64(cursor)?)),
TAG_SHORT_STRING | TAG_LONG_STRING => {
let s = read_string_with_dedup(cursor, string_table)?;
vm.create_bytes(s.as_bytes())
.map_err(|e| format!("failed to create string: {}", e))
}
TAG_BINARY => {
let len = read_u32(cursor)? as usize;
let mut bytes = vec![0u8; len];
cursor
.read_exact(&mut bytes)
.map_err(|e| format!("failed to read binary data: {}", e))?;
vm.create_bytes(&bytes)
.map_err(|e| format!("failed to create binary: {}", e))
}
_ => Err(format!("unknown constant tag: {}", tag)),
}
}
fn read_chunk_with_strings(
cursor: &mut Cursor<&[u8]>,
strings: &mut Vec<(usize, String)>,
) -> Result<Chunk, String> {
let code_len = read_u32(cursor)? as usize;
let mut code = Vec::with_capacity(code_len);
for _ in 0..code_len {
code.push(Instruction::from_u32(read_u32(cursor)?));
}
let const_len = read_u32(cursor)? as usize;
let mut constants = Vec::with_capacity(const_len);
for i in 0..const_len {
constants.push(read_constant_with_strings(cursor, i, strings)?);
}
let upvalue_count = read_u32(cursor)? as usize;
let param_count = read_u32(cursor)? as usize;
let is_vararg = read_u8(cursor)? != 0;
let needs_vararg_table = read_u8(cursor)? != 0;
let max_stack_size = read_u32(cursor)? as usize;
let linedefined = read_u32(cursor)? as usize;
let lastlinedefined = read_u32(cursor)? as usize;
let desc_len = read_u32(cursor)? as usize;
let mut upvalue_descs = Vec::with_capacity(desc_len);
for _ in 0..desc_len {
let name = read_string(cursor)?;
let is_local = read_u8(cursor)? != 0;
let index = read_u32(cursor)?;
upvalue_descs.push(UpvalueDesc {
name,
is_local,
index,
});
}
let child_len = read_u32(cursor)? as usize;
let mut child_protos = Vec::with_capacity(child_len);
for _ in 0..child_len {
child_protos.push(detached_proto(read_chunk_with_strings(cursor, strings)?));
}
let source_name = read_optional_string(cursor)?;
let locals_len = read_u32(cursor)? as usize;
let mut locals = Vec::with_capacity(locals_len);
for _ in 0..locals_len {
let name = read_string(cursor)?;
let startpc = read_u32(cursor)?;
let endpc = read_u32(cursor)?;
locals.push(LocVar {
name,
startpc,
endpc,
});
}
let line_len = read_u32(cursor)? as usize;
let mut line_info = Vec::with_capacity(line_len);
for _ in 0..line_len {
line_info.push(read_u32(cursor)?);
}
let mut chunk = Chunk {
code,
constants,
locals,
upvalue_count,
param_count,
is_vararg,
needs_vararg_table,
use_hidden_vararg: false,
max_stack_size,
child_protos,
upvalue_descs,
source_name,
line_info,
linedefined,
lastlinedefined,
proto_data_size: 0,
};
chunk.compute_proto_data_size();
Ok(chunk)
}
fn write_u32(buf: &mut Vec<u8>, value: u32) {
buf.extend_from_slice(&value.to_le_bytes());
}
fn write_i64(buf: &mut Vec<u8>, value: i64) {
buf.extend_from_slice(&value.to_le_bytes());
}
fn write_f64(buf: &mut Vec<u8>, value: f64) {
buf.extend_from_slice(&value.to_le_bytes());
}
fn write_string(buf: &mut Vec<u8>, s: &str) {
let bytes = s.as_bytes();
write_u32(buf, bytes.len() as u32);
buf.extend_from_slice(bytes);
}
fn read_u8(cursor: &mut Cursor<&[u8]>) -> Result<u8, String> {
let mut buf = [0u8; 1];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
Ok(buf[0])
}
fn read_u32(cursor: &mut Cursor<&[u8]>) -> Result<u32, String> {
let mut buf = [0u8; 4];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
Ok(u32::from_le_bytes(buf))
}
fn read_i64(cursor: &mut Cursor<&[u8]>) -> Result<i64, String> {
let mut buf = [0u8; 8];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
Ok(i64::from_le_bytes(buf))
}
fn read_f64(cursor: &mut Cursor<&[u8]>) -> Result<f64, String> {
let mut buf = [0u8; 8];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
Ok(f64::from_le_bytes(buf))
}
fn read_string(cursor: &mut Cursor<&[u8]>) -> Result<String, String> {
let len = read_u32(cursor)? as usize;
let mut buf = vec![0u8; len];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
String::from_utf8(buf).map_err(|e| format!("invalid utf8: {}", e))
}
fn read_optional_string(cursor: &mut Cursor<&[u8]>) -> Result<Option<String>, String> {
let len = read_u32(cursor)? as usize;
if len == 0 {
return Ok(None);
}
let mut buf = vec![0u8; len];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
Ok(Some(
String::from_utf8(buf).map_err(|e| format!("invalid utf8: {}", e))?,
))
}
fn read_string_with_dedup(
cursor: &mut Cursor<&[u8]>,
string_table: &mut Vec<String>,
) -> Result<String, String> {
let len = read_u32(cursor)? as usize;
if len == 0 {
let index = read_u32(cursor)? as usize;
if index == 0 {
let s = String::new();
string_table.push(s.clone());
return Ok(s);
}
if index > string_table.len() {
eprintln!(
"ERROR: String reference index {} out of range (table size: {})",
index,
string_table.len()
);
eprintln!("String table contents (first 50):");
for (i, s) in string_table.iter().take(50).enumerate() {
eprintln!(
" [{}]: {:?}",
i + 1,
if s.len() > 50 { &s[..50] } else { s }
);
}
return Err(format!("invalid string reference index: {}", index));
}
Ok(string_table[index - 1].clone())
} else {
let mut buf = vec![0u8; len];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
let s = String::from_utf8(buf).map_err(|e| format!("invalid utf8: {}", e))?;
string_table.push(s.clone());
Ok(s)
}
}
fn read_optional_string_with_dedup(
cursor: &mut Cursor<&[u8]>,
string_table: &mut Vec<String>,
) -> Result<Option<String>, String> {
let len = read_u32(cursor)? as usize;
if len == 0 {
let index_u32 = read_u32(cursor)?;
if index_u32 == 0 {
return Ok(None);
} else {
let index = index_u32 as usize;
if index > string_table.len() {
eprintln!(
"ERROR in read_optional: String reference index {} out of range (table size: {})",
index,
string_table.len()
);
return Err(format!("invalid string reference index: {}", index));
}
return Ok(Some(string_table[index - 1].clone()));
}
}
let mut buf = vec![0u8; len];
cursor
.read_exact(&mut buf)
.map_err(|e| format!("read error: {}", e))?;
let s = String::from_utf8(buf).map_err(|e| format!("invalid utf8: {}", e))?;
string_table.push(s.clone());
Ok(Some(s))
}
fn read_constant_with_dedup(
cursor: &mut Cursor<&[u8]>,
string_table: &mut Vec<String>,
) -> Result<LuaValue, String> {
let tag = read_u8(cursor)?;
match tag {
TAG_NIL => Ok(LuaValue::nil()),
TAG_BOOL_FALSE => Ok(LuaValue::boolean(false)),
TAG_BOOL_TRUE => Ok(LuaValue::boolean(true)),
TAG_INTEGER => Ok(LuaValue::integer(read_i64(cursor)?)),
TAG_FLOAT => Ok(LuaValue::number(read_f64(cursor)?)),
TAG_SHORT_STRING | TAG_LONG_STRING => {
let _s = read_string_with_dedup(cursor, string_table)?;
Ok(LuaValue::nil())
}
_ => Err(format!("unknown constant tag: {}", tag)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialize_empty_chunk() {
let chunk = Chunk::new();
let bytes = serialize_chunk(&chunk, false).unwrap();
let restored = deserialize_chunk(&bytes).unwrap();
assert_eq!(restored.code.len(), 0);
assert_eq!(restored.constants.len(), 0);
}
}