use super::callinfo::CallInfo;
use super::closure::Closure;
use super::gc::arena::Arena;
use super::instructions::{Instruction, NO_REG, OpCode, index_k, is_k};
use super::proto::Proto;
use super::state::{Gc, LuaState};
use super::string::LuaString;
use super::value::Val;
pub fn get_local_name(proto: &Proto, local_number: usize, pc: usize) -> Option<&str> {
let mut remaining = local_number;
#[allow(clippy::cast_possible_truncation)]
let pc_u32 = pc as u32;
for var in &proto.local_vars {
if var.start_pc > pc_u32 {
break;
}
if pc_u32 < var.end_pc {
remaining -= 1;
if remaining == 0 {
return Some(&var.name);
}
}
}
None
}
pub fn symbexec(proto: &Proto, lastpc: usize, reg: u32) -> u32 {
let mut last = if proto.code.is_empty() {
0
} else {
proto.code.len() - 1
};
let mut pc: usize = 0;
while pc < lastpc {
let raw = proto.code[pc];
let instr = Instruction::from_raw(raw);
let op = instr.opcode();
let a = instr.a();
if op.sets_register_a() && a == reg {
last = pc;
}
match op {
OpCode::LoadNil => {
let b = instr.b();
if a <= reg && reg <= b {
last = pc;
}
}
OpCode::OpSelf => {
if reg == a + 1 {
last = pc;
}
}
OpCode::Call | OpCode::TailCall => {
if reg >= a {
last = pc;
}
}
OpCode::TForLoop => {
if reg >= a + 2 {
last = pc;
}
}
OpCode::Jmp | OpCode::ForLoop | OpCode::ForPrep => {
let sbx = instr.sbx();
#[allow(clippy::cast_possible_wrap)]
let dest = pc as i64 + 1 + i64::from(sbx);
#[allow(clippy::cast_sign_loss)]
if reg != NO_REG && dest > pc as i64 && (dest as usize) <= lastpc {
pc = dest as usize;
continue; }
}
OpCode::SetList => {
let c = instr.c();
if c == 0 {
pc += 1;
}
}
OpCode::Closure => {
if let Some(child) = proto.protos.get(instr.bx() as usize) {
pc += usize::from(child.num_upvalues);
}
}
_ => {}
}
pc += 1;
}
proto.code.get(last).copied().unwrap_or(0)
}
pub fn kname(proto: &Proto, c: u32, string_arena: &Arena<LuaString>) -> String {
if is_k(c) {
let idx = index_k(c) as usize;
if let Some(Val::Str(r)) = proto.constants.get(idx)
&& let Some(s) = string_arena.get(*r)
{
return String::from_utf8_lossy(s.data()).into_owned();
}
}
"?".to_string()
}
pub fn getobjname(
proto: &Proto,
pc: usize,
stackpos: u32,
string_arena: &Arena<LuaString>,
) -> Option<(&'static str, String)> {
if let Some(name) = get_local_name(proto, (stackpos + 1) as usize, pc) {
return Some(("local", name.to_string()));
}
let raw = symbexec(proto, pc, stackpos);
let instr = Instruction::from_raw(raw);
let op = instr.opcode();
match op {
OpCode::GetGlobal => {
let bx = instr.bx() as usize;
if let Some(Val::Str(r)) = proto.constants.get(bx)
&& let Some(s) = string_arena.get(*r)
{
return Some(("global", String::from_utf8_lossy(s.data()).into_owned()));
}
}
OpCode::Move => {
let a = instr.a();
let b = instr.b();
if b < a {
return getobjname(proto, pc, b, string_arena);
}
}
OpCode::GetTable => {
let c = instr.c();
let name = kname(proto, c, string_arena);
return Some(("field", name));
}
OpCode::GetUpval => {
let b = instr.b() as usize;
if let Some(name) = proto.upvalue_names.get(b) {
return Some(("upvalue", name.clone()));
}
return Some(("upvalue", "?".to_string()));
}
OpCode::OpSelf => {
let c = instr.c();
let name = kname(proto, c, string_arena);
return Some(("method", name));
}
_ => {}
}
None
}
pub fn getfuncname(
state: &LuaState,
ci_idx: usize,
string_arena: &Arena<LuaString>,
) -> Option<(&'static str, String)> {
if ci_idx == 0 {
return None;
}
let ci = &state.call_stack[ci_idx];
if ci.tail_calls > 0 {
return None;
}
let caller_ci = &state.call_stack[ci_idx - 1];
let caller_func = state.stack_get(caller_ci.func);
let caller_proto = match caller_func {
Val::Function(r) => {
let cl = state.gc.closures.get(r)?;
match cl {
Closure::Lua(lcl) => crate::vm::proto::ProtoRef::clone(&lcl.proto),
Closure::Rust(_) => return None,
}
}
_ => return None,
};
let caller_pc = caller_ci.saved_pc;
if caller_pc == 0 {
return None;
}
let call_pc = caller_pc - 1;
if call_pc >= caller_proto.code.len() {
return None;
}
let call_instr = Instruction::from_raw(caller_proto.code[call_pc]);
let call_op = call_instr.opcode();
match call_op {
OpCode::Call | OpCode::TailCall => {
let a = call_instr.a();
getobjname(&caller_proto, call_pc, a, string_arena)
}
OpCode::TForLoop => {
let a = call_instr.a();
getobjname(&caller_proto, call_pc, a, string_arena)
}
_ => None,
}
}
pub fn getfuncname_raw(
call_stack: &[CallInfo],
stack: &[Val],
gc: &Gc,
ci_idx: usize,
string_arena: &Arena<LuaString>,
) -> Option<(&'static str, String)> {
if ci_idx == 0 {
return None;
}
let ci = &call_stack[ci_idx];
if ci.tail_calls > 0 {
return None;
}
let caller_ci = &call_stack[ci_idx - 1];
let caller_func = if caller_ci.func < stack.len() {
stack[caller_ci.func]
} else {
return None;
};
let caller_proto = match caller_func {
Val::Function(r) => {
let cl = gc.closures.get(r)?;
match cl {
Closure::Lua(lcl) => crate::vm::proto::ProtoRef::clone(&lcl.proto),
Closure::Rust(_) => return None,
}
}
_ => return None,
};
let caller_pc = caller_ci.saved_pc;
if caller_pc == 0 {
return None;
}
let call_pc = caller_pc - 1;
if call_pc >= caller_proto.code.len() {
return None;
}
let call_instr = Instruction::from_raw(caller_proto.code[call_pc]);
let call_op = call_instr.opcode();
match call_op {
OpCode::Call | OpCode::TailCall | OpCode::TForLoop => {
let a = call_instr.a();
getobjname(&caller_proto, call_pc, a, string_arena)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vm::instructions::OpCode;
use crate::vm::proto::LocalVar;
#[test]
fn get_local_name_basic() {
let proto = Proto {
local_vars: vec![
LocalVar {
name: "x".into(),
start_pc: 0,
end_pc: 5,
},
LocalVar {
name: "y".into(),
start_pc: 1,
end_pc: 5,
},
],
..Proto::new("test")
};
assert_eq!(get_local_name(&proto, 1, 0), Some("x"));
assert_eq!(get_local_name(&proto, 2, 0), None);
assert_eq!(get_local_name(&proto, 1, 1), Some("x"));
assert_eq!(get_local_name(&proto, 2, 1), Some("y"));
assert_eq!(get_local_name(&proto, 1, 5), None);
}
#[test]
fn get_local_name_not_found() {
let proto = Proto::new("test");
assert_eq!(get_local_name(&proto, 1, 0), None);
}
#[test]
fn symbexec_move() {
let move_instr = Instruction::abc(OpCode::Move, 0, 1, 0);
let ret = Instruction::abc(OpCode::Return, 0, 1, 0);
let proto = Proto {
code: vec![move_instr.raw(), ret.raw()],
..Proto::new("test")
};
let result = symbexec(&proto, 1, 0);
let instr = Instruction::from_raw(result);
assert_eq!(instr.opcode(), OpCode::Move);
}
#[test]
fn symbexec_getglobal() {
let getglobal = Instruction::a_bx(OpCode::GetGlobal, 0, 0);
let ret = Instruction::abc(OpCode::Return, 0, 1, 0);
let proto = Proto {
code: vec![getglobal.raw(), ret.raw()],
..Proto::new("test")
};
let result = symbexec(&proto, 1, 0);
let instr = Instruction::from_raw(result);
assert_eq!(instr.opcode(), OpCode::GetGlobal);
}
#[test]
fn symbexec_loadnil() {
let loadnil = Instruction::abc(OpCode::LoadNil, 0, 2, 0);
let ret = Instruction::abc(OpCode::Return, 0, 1, 0);
let proto = Proto {
code: vec![loadnil.raw(), ret.raw()],
..Proto::new("test")
};
let result = symbexec(&proto, 1, 1);
let instr = Instruction::from_raw(result);
assert_eq!(instr.opcode(), OpCode::LoadNil);
}
#[test]
fn kname_with_constant() {
use crate::vm::gc::Color;
use crate::vm::gc::arena::Arena;
use crate::vm::instructions::rk_as_k;
let mut arena = Arena::new();
let r = arena.alloc(LuaString::new(b"hello", 0), Color::White0);
let proto = Proto {
constants: vec![Val::Str(r)],
..Proto::new("test")
};
let name = kname(&proto, rk_as_k(0), &arena);
assert_eq!(name, "hello");
}
#[test]
fn kname_with_register() {
let arena = Arena::new();
let proto = Proto::new("test");
let name = kname(&proto, 5, &arena);
assert_eq!(name, "?");
}
}