use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::Vm;
const HEADER_53: [u8; 33] = [
0x1b, b'L', b'u', b'a', 0x53, 0x00, 0x19, 0x93, b'\r', b'\n', 0x1a, b'\n', 4, 8, 4, 8, 8, 0x78, 0x56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x28, 0x77, 0x40,
];
fn enc(op: u8, a: u32, b: u32, c: u32) -> u32 {
debug_assert!(op < 64);
debug_assert!(a < 256);
debug_assert!(b < 512);
debug_assert!(c < 512);
(op as u32) | (a << 6) | (c << 14) | (b << 23)
}
fn enc_bx(op: u8, a: u32, bx: u32) -> u32 {
debug_assert!(bx < (1 << 18));
(op as u32) | (a << 6) | (bx << 14)
}
#[allow(dead_code)]
fn enc_sbx(op: u8, a: u32, sbx: i32) -> u32 {
let bx = (sbx + 131071) as u32;
enc_bx(op, a, bx)
}
#[allow(dead_code)]
fn rk(idx: u32) -> u32 {
debug_assert!(idx < 256);
idx | (1 << 8)
}
fn puc53_str(s: &[u8]) -> Vec<u8> {
let mut v = Vec::with_capacity(1 + s.len() + 8);
if s.is_empty() {
v.push(0);
return v;
}
let sized = (s.len() as u64) + 1;
if sized < 0xFF {
v.push(sized as u8);
} else {
v.push(0xFF);
v.extend_from_slice(&sized.to_le_bytes());
}
v.extend_from_slice(s);
v
}
fn put_i32(out: &mut Vec<u8>, v: i32) {
out.extend_from_slice(&v.to_le_bytes());
}
fn put_u32(out: &mut Vec<u8>, v: u32) {
out.extend_from_slice(&v.to_le_bytes());
}
fn put_i64(out: &mut Vec<u8>, v: i64) {
out.extend_from_slice(&v.to_le_bytes());
}
fn put_f64(out: &mut Vec<u8>, v: f64) {
out.extend_from_slice(&v.to_bits().to_le_bytes());
}
const OP_LOADK: u8 = 1;
const OP_LOADBOOL: u8 = 3;
#[allow(dead_code)]
const OP_GETTABUP: u8 = 6;
#[allow(dead_code)]
const OP_ADD: u8 = 13;
const OP_RETURN: u8 = 38;
fn build_return_42_chunk() -> Vec<u8> {
let mut body = Vec::new();
body.push(1u8);
body.extend_from_slice(&puc53_str(b"@test")); put_i32(&mut body, 0); put_i32(&mut body, 0); body.push(0); body.push(1); body.push(2);
put_i32(&mut body, 2);
put_u32(&mut body, enc_bx(OP_LOADK, 0, 0)); put_u32(&mut body, enc(OP_RETURN, 0, 2, 0));
put_i32(&mut body, 1);
body.push(0x13); put_i64(&mut body, 42);
put_i32(&mut body, 1);
body.push(1); body.push(0);
put_i32(&mut body, 0);
put_i32(&mut body, 2);
put_i32(&mut body, 1);
put_i32(&mut body, 1);
put_i32(&mut body, 0);
put_i32(&mut body, 1);
body.extend_from_slice(&puc53_str(b"_ENV"));
let mut chunk = Vec::new();
chunk.extend_from_slice(&HEADER_53);
chunk.extend(body);
chunk
}
fn build_return_float_chunk() -> Vec<u8> {
let mut body = Vec::new();
body.push(1u8);
body.extend_from_slice(&puc53_str(b"@test"));
put_i32(&mut body, 0);
put_i32(&mut body, 0);
body.push(0);
body.push(1);
body.push(2);
put_i32(&mut body, 2);
put_u32(&mut body, enc_bx(OP_LOADK, 0, 0));
put_u32(&mut body, enc(OP_RETURN, 0, 2, 0));
put_i32(&mut body, 1);
body.push(3); put_f64(&mut body, 3.5);
put_i32(&mut body, 1);
body.push(1);
body.push(0);
put_i32(&mut body, 0);
put_i32(&mut body, 2);
put_i32(&mut body, 1);
put_i32(&mut body, 1);
put_i32(&mut body, 0);
put_i32(&mut body, 1);
body.extend_from_slice(&puc53_str(b"_ENV"));
let mut chunk = Vec::new();
chunk.extend_from_slice(&HEADER_53);
chunk.extend(body);
chunk
}
#[test]
fn rejects_when_puc_loading_disabled() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
let chunk = build_return_42_chunk();
let err = vm.load(&chunk, b"=test").unwrap_err();
let s = format!("{}", String::from_utf8_lossy(&err.msg));
assert!(
s.contains("PUC bytecode") || s.contains("disabled"),
"expected PUC-disabled message, got: {s}"
);
}
#[test]
fn rejects_bad_luac_int() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let mut chunk = build_return_42_chunk();
chunk[18] = 0xFF;
let err = vm.load(&chunk, b"=test").unwrap_err();
let s = String::from_utf8_lossy(&err.msg).into_owned();
assert!(
s.contains("LUAC_INT") || s.contains("endianness"),
"got: {s}"
);
}
#[test]
fn rejects_bad_sizeof_size_t() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let mut chunk = build_return_42_chunk();
chunk[13] = 4;
let err = vm.load(&chunk, b"=test").unwrap_err();
let s = String::from_utf8_lossy(&err.msg).into_owned();
assert!(s.contains("size_t"), "got: {s}");
}
#[test]
fn rejects_bad_sizeof_int() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let mut chunk = build_return_42_chunk();
chunk[12] = 8;
let err = vm.load(&chunk, b"=test").unwrap_err();
let s = String::from_utf8_lossy(&err.msg).into_owned();
assert!(s.contains("sizeof(int)"), "got: {s}");
}
#[test]
fn rejects_bad_luac_num() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let mut chunk = build_return_42_chunk();
chunk[32] = 0xFF;
let err = vm.load(&chunk, b"=test").unwrap_err();
let s = String::from_utf8_lossy(&err.msg).into_owned();
assert!(
s.contains("LUAC_NUM") || s.contains("float format"),
"got: {s}"
);
}
#[test]
fn loads_return_int_chunk() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let chunk = build_return_42_chunk();
vm.load(&chunk, b"=test").expect("undump succeeds");
}
#[test]
fn loads_return_float_chunk() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let chunk = build_return_float_chunk();
vm.load(&chunk, b"=test").expect("undump succeeds");
}
#[test]
fn rejects_wrong_version_byte() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let mut chunk = build_return_42_chunk();
chunk[4] = 0x52; let err = vm.load(&chunk, b"=test").unwrap_err();
let s = String::from_utf8_lossy(&err.msg).into_owned();
assert!(s.contains("5.2") || s.contains("LB5"), "got: {s}");
}
#[test]
fn end_to_end_returns_42() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let chunk = build_return_42_chunk();
let closure = vm.load(&chunk, b"=test").expect("undump");
let result = vm
.call_value(Value::Closure(closure), &[])
.expect("call succeeds");
assert_eq!(result.len(), 1, "expected one return value");
match result[0] {
Value::Int(42) => {}
other => panic!("expected Int(42), got {other:?}"),
}
}
#[test]
fn end_to_end_returns_float() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let chunk = build_return_float_chunk();
let closure = vm.load(&chunk, b"=test").expect("undump");
let result = vm
.call_value(Value::Closure(closure), &[])
.expect("call succeeds");
assert_eq!(result.len(), 1);
match result[0] {
Value::Float(f) if (f - 3.5).abs() < 1e-9 => {}
other => panic!("expected Float(3.5), got {other:?}"),
}
}
fn build_return_true_via_loadbool_skip_chunk() -> Vec<u8> {
let mut body = Vec::new();
body.push(1u8);
body.extend_from_slice(&puc53_str(b"@test"));
put_i32(&mut body, 0); put_i32(&mut body, 0); body.push(0); body.push(1); body.push(1);
put_i32(&mut body, 3);
put_u32(&mut body, enc(OP_LOADBOOL, 0, 1, 1)); put_u32(&mut body, enc(OP_LOADBOOL, 0, 0, 0)); put_u32(&mut body, enc(OP_RETURN, 0, 2, 0));
put_i32(&mut body, 0);
put_i32(&mut body, 1);
body.push(1);
body.push(0);
put_i32(&mut body, 0);
put_i32(&mut body, 3);
put_i32(&mut body, 1);
put_i32(&mut body, 1);
put_i32(&mut body, 1);
put_i32(&mut body, 0);
put_i32(&mut body, 1);
body.extend_from_slice(&puc53_str(b"_ENV"));
let mut chunk = Vec::new();
chunk.extend_from_slice(&HEADER_53);
chunk.extend(body);
chunk
}
#[test]
fn end_to_end_loadbool_true_skip_returns_true() {
let mut vm = Vm::new(LuaVersion::Lua54);
vm.set_bytecode_loading(true);
vm.set_puc_bytecode_loading(true);
let chunk = build_return_true_via_loadbool_skip_chunk();
let closure = vm.load(&chunk, b"=test").expect("undump");
let result = vm
.call_value(Value::Closure(closure), &[])
.expect("call succeeds");
assert_eq!(result.len(), 1, "expected one return value");
match result[0] {
Value::Bool(true) => {}
other => panic!("expected Bool(true) (skipped LoadFalse), got {other:?}"),
}
}
#[test]
#[ignore = "requires luac5.3 on PATH (set LUAC53 env var)"]
fn end_to_end_luac53_corpus() {
}