#[allow(unused_imports)] use crate::prelude::*;
use crate::state::{
CallInfo, GcRef, LuaClosure, LuaClosureLua, LuaProto, LuaState, LuaTable, LuaValue,
UpVal, CIST_C, CIST_FIN, CIST_HOOKED, CIST_HOOKYIELD, CIST_TAIL, CIST_TRAN,
};
use lua_types::{CallInfoIdx, StackIdx, LuaString};
use lua_types::error::LuaError;
use lua_types::opcode::Instruction;
use crate::vm::InstructionExt;
const ABS_LINE_INFO: i8 = -0x80_i8;
const MAX_IWTH_ABS: i32 = 128;
const LUA_IDSIZE: usize = 60;
const LUA_MASKLINE: u8 = 1 << 2;
const LUA_MASKCOUNT: u8 = 1 << 3;
const LUA_MASKCALL: u8 = 1 << 0;
const LUA_HOOKLINE: i32 = 2;
const LUA_HOOKCOUNT: i32 = 3;
const LUA_YIELD_STATUS: i32 = 1;
const LUA_ENV: &[u8] = b"_ENV";
fn runtime_bytes(msg: Vec<u8>) -> LuaError {
LuaError::Runtime(lua_types::LuaValue::Str(lua_types::GcRef::new(
lua_types::LuaString::from_bytes(msg),
)))
}
pub(crate) fn prefixed_runtime_pub(state: &LuaState, msg: Vec<u8>) -> LuaError {
prefixed_runtime(state, msg)
}
fn prefixed_runtime(state: &LuaState, msg: Vec<u8>) -> LuaError {
let ci_idx = state.current_ci_idx();
let ci = state.get_ci(ci_idx).clone();
if !ci.is_lua() {
return runtime_bytes(msg);
}
let proto = ci_lua_proto(&ci, state);
let src = proto.source_string();
let line = get_current_line(&ci, state);
let prefixed = add_info(
None,
&msg,
src.map(|s| &**s),
line,
);
runtime_bytes(prefixed)
}
pub fn c_api_runtime(state: &LuaState, msg: Vec<u8>) -> LuaError {
let ci_idx = state.current_ci_idx();
if let Some(parent_idx) = state.prev_ci(ci_idx) {
let parent_ci = state.get_ci(parent_idx).clone();
if parent_ci.is_lua() {
let proto = ci_lua_proto(&parent_ci, state);
let src = proto.source_string();
let line = get_current_line(&parent_ci, state);
let prefixed = add_info(None, &msg, src.map(|s| &**s), line);
return runtime_bytes(prefixed);
}
}
runtime_bytes(msg)
}
#[allow(dead_code)]
fn find_func_in_table(table: &LuaTable, target: &LuaValue, prefix: &[u8], depth: u8) -> Option<Vec<u8>> {
let mut key = LuaValue::Nil;
loop {
let (k, v) = match table.next_pair(&key) {
Some(pair) => pair,
None => break,
};
if !matches!(v, LuaValue::Nil) {
let key_bytes: Option<Vec<u8>> = match &k {
LuaValue::Str(s) => Some(s.as_bytes().to_vec()),
_ => None,
};
if let Some(kb) = key_bytes {
if &v == target {
if prefix.is_empty() {
return Some(kb);
}
let mut result = prefix.to_vec();
result.push(b'.');
result.extend_from_slice(&kb);
return Some(result);
}
if depth > 0 {
if let LuaValue::Table(sub) = &v {
let new_prefix = if prefix.is_empty() {
kb.clone()
} else {
let mut p = prefix.to_vec();
p.push(b'.');
p.extend_from_slice(&kb);
p
};
if let Some(name) = find_func_in_table(&**sub, target, &new_prefix, depth - 1) {
return Some(name);
}
}
}
}
}
key = k;
}
None
}
#[allow(dead_code)]
fn find_func_name_in_globals(state: &LuaState, func_val: &LuaValue) -> Option<Vec<u8>> {
let globals = state.global().globals.clone();
if let LuaValue::Table(globals_table) = globals {
find_func_in_table(&*globals_table, func_val, b"", 1)
} else {
None
}
}
fn find_func_name_in_loaded(state: &LuaState, func_val: &LuaValue) -> Option<Vec<u8>> {
let registry = state.global().l_registry.clone();
let loaded = match registry {
LuaValue::Table(ref reg_table) => reg_table.get_str_bytes(b"_LOADED"),
_ => return None,
};
let loaded_table = match loaded {
LuaValue::Table(t) => t,
_ => return None,
};
find_func_in_table(&*loaded_table, func_val, b"", 1)
}
pub fn arg_error_impl(state: &mut LuaState, mut arg: i32, extramsg: &[u8]) -> LuaError {
let mut ar = LuaDebug::default();
if !get_stack(state, 0, &mut ar) {
let msg = format!("bad argument #{} ({})", arg, String::from_utf8_lossy(extramsg));
return c_api_runtime(state, msg.into_bytes());
}
get_info(state, b"n", &mut ar);
if ar.namewhat.as_deref() == Some(b"method") {
arg -= 1;
if arg == 0 {
let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
let msg = format!(
"calling '{}' on bad self ({})",
String::from_utf8_lossy(&name),
String::from_utf8_lossy(extramsg)
);
return c_api_runtime(state, msg.into_bytes());
}
}
let fname = ar.name.clone().or_else(|| {
let ci_idx = ar.i_ci?;
let func_slot = state.get_ci(ci_idx).func;
let func_val = state.get_at(func_slot).clone();
let found = find_func_name_in_loaded(state, &func_val)?;
if found.starts_with(b"_G.") {
Some(found[3..].to_vec())
} else {
Some(found)
}
}).unwrap_or_else(|| b"?".to_vec());
let msg = format!(
"bad argument #{} to '{}' ({})",
arg,
String::from_utf8_lossy(&fname),
String::from_utf8_lossy(extramsg)
);
c_api_runtime(state, msg.into_bytes())
}
fn runtime_from_top(state: &mut crate::state::LuaState) -> LuaError {
let v = state.pop();
LuaError::Runtime(v)
}
pub struct LuaDebug {
pub event: i32,
pub name: Option<Vec<u8>>,
pub namewhat: Option<&'static [u8]>,
pub what: Option<&'static [u8]>,
pub source: Option<Vec<u8>>,
pub srclen: usize,
pub currentline: i32,
pub linedefined: i32,
pub lastlinedefined: i32,
pub nups: u8,
pub nparams: u8,
pub isvararg: bool,
pub istailcall: bool,
pub ftransfer: u16,
pub ntransfer: u16,
pub short_src: [u8; LUA_IDSIZE],
pub i_ci: Option<CallInfoIdx>,
}
impl Default for LuaDebug {
fn default() -> Self {
LuaDebug {
event: 0,
name: None,
namewhat: None,
what: None,
source: None,
srclen: 0,
currentline: -1,
linedefined: -1,
lastlinedefined: -1,
nups: 0,
nparams: 0,
isvararg: false,
istailcall: false,
ftransfer: 0,
ntransfer: 0,
short_src: [0u8; LUA_IDSIZE],
i_ci: None,
}
}
}
#[inline]
fn is_lua_closure(cl: Option<&LuaClosure>) -> bool {
matches!(cl, Some(LuaClosure::Lua(_)))
}
fn current_pc(ci: &CallInfo) -> i32 {
debug_assert!(ci.is_lua());
ci.saved_pc().saturating_sub(1) as i32
}
fn get_baseline(f: &LuaProto, pc: i32, basepc: &mut i32) -> i32 {
if f.abslineinfo.is_empty() || pc < f.abslineinfo[0].pc {
*basepc = -1;
return f.linedefined;
}
let mut i = (pc as u32 / MAX_IWTH_ABS as u32).saturating_sub(1) as usize;
debug_assert!(
i < f.abslineinfo.len() && f.abslineinfo[i].pc <= pc,
"getbaseline: estimate is not a lower bound"
);
while i + 1 < f.abslineinfo.len() && pc >= f.abslineinfo[i + 1].pc {
i += 1;
}
*basepc = f.abslineinfo[i].pc;
f.abslineinfo[i].line
}
pub(crate) fn get_func_line(f: &LuaProto, pc: i32) -> i32 {
if f.lineinfo.is_empty() {
return -1;
}
let mut basepc: i32 = 0;
let mut baseline = get_baseline(f, pc, &mut basepc);
while basepc < pc {
basepc += 1;
debug_assert!(
f.lineinfo[basepc as usize] != ABS_LINE_INFO,
"get_func_line: hit ABSLINEINFO in incremental walk"
);
baseline += f.lineinfo[basepc as usize] as i32;
}
baseline
}
fn get_current_line(ci: &CallInfo, state: &LuaState) -> i32 {
let proto = ci_lua_proto(ci, state);
get_func_line(&proto, current_pc(ci))
}
fn set_traps(state: &mut LuaState) {
for ci in state.call_stack_mut().iter_mut() {
if ci.is_lua() {
ci.set_trap(true);
}
}
}
pub fn set_hook(
state: &mut LuaState,
func: Option<Box<dyn FnMut(&mut LuaState, &LuaDebug)>>,
mask: i32,
count: i32,
) {
let (func, mask) = if func.is_none() || mask == 0 {
(None, 0i32)
} else {
(func, mask)
};
state.set_hook(func);
state.set_base_hook_count(count);
state.reset_hook_count();
state.set_hook_mask(mask as u8);
if mask != 0 {
set_traps(state);
}
}
pub fn get_hook_installed(state: &LuaState) -> bool {
state.hook().is_some()
}
pub fn get_hook_mask(state: &LuaState) -> i32 {
state.hook_mask() as i32
}
pub fn get_hook_count(state: &LuaState) -> i32 {
state.base_hook_count()
}
pub fn get_stack(state: &LuaState, level: i32, ar: &mut LuaDebug) -> bool {
if level < 0 {
return false;
}
let mut remaining = level;
let mut ci_idx = state.current_ci_idx();
loop {
if remaining == 0 {
break;
}
match state.prev_ci(ci_idx) {
Some(prev) => {
ci_idx = prev;
remaining -= 1;
}
None => {
return false;
}
}
}
if !state.is_base_ci(ci_idx) {
ar.i_ci = Some(ci_idx);
true
} else {
false
}
}
fn upval_name(p: &LuaProto, uv: usize) -> &[u8] {
debug_assert!(uv < p.upvalues.len(), "upval_name: index out of range");
p.upvalues[uv].name.as_ref().map_or(b"?" as &[u8], |s| s.as_bytes())
}
fn find_vararg(state: &LuaState, ci: &CallInfo, n: i32) -> Option<(StackIdx, &'static [u8])> {
let proto = ci_lua_proto(ci, state);
if proto.is_vararg {
let nextra = ci.nextra_args();
if n >= -(nextra as i32) {
let pos = ci.func - (nextra + n + 1);
return Some((pos, b"(vararg)" as &[u8]));
}
}
None
}
pub(crate) fn find_local(
state: &LuaState,
ci_idx: CallInfoIdx,
n: i32,
pos: Option<&mut StackIdx>,
) -> Option<Vec<u8>> {
let ci = state.get_ci(ci_idx);
let base = ci.func + 1;
let mut name: Option<Vec<u8>> = None;
if ci.is_lua() {
if n < 0 {
if let Some((vpos, vname)) = find_vararg(state, ci, n) {
if let Some(out_pos) = pos {
*out_pos = vpos;
}
return Some(vname.to_vec());
}
return None;
} else {
let proto = ci_lua_proto(ci, state);
let pc = current_pc(ci);
name = crate::func::get_local_name(&proto, n, pc).map(|s| s.to_vec());
}
}
if name.is_none() {
let limit: u32 = if ci_idx == state.current_ci_idx() {
state.top_idx().0
} else {
ci.next
.map(|next| state.get_ci(next).func.0)
.unwrap_or_else(|| state.top_idx().0)
};
if n > 0 && limit.saturating_sub(base.0) >= n as u32 {
name = Some(if ci.is_lua() { b"(temporary)".to_vec() } else { b"(C temporary)".to_vec() });
} else {
return None;
}
}
if let Some(out_pos) = pos {
*out_pos = base + (n - 1);
}
name
}
pub fn get_local(state: &mut LuaState, ar: Option<&LuaDebug>, n: i32) -> Option<Vec<u8>> {
if ar.is_none() {
let top_val = state.peek_top();
if !matches!(top_val, LuaValue::Function(LuaClosure::Lua(_))) {
return None;
}
let name_owned: Option<Vec<u8>> = {
let cl = match top_val {
LuaValue::Function(LuaClosure::Lua(ref cl)) => cl.clone(),
_ => unreachable!(),
};
get_local_name_from_closure(&cl, n, 0).map(|s| s.to_vec())
};
return name_owned;
}
let ar = ar.unwrap();
let ci_idx = ar.i_ci?;
let mut pos = StackIdx(0);
let name_owned: Option<Vec<u8>> = find_local(state, ci_idx, n, Some(&mut pos));
if name_owned.is_some() {
let val = state.get_at(pos).clone();
state.push(val);
}
name_owned
}
pub fn set_local(state: &mut LuaState, ar: &LuaDebug, n: i32) -> Option<Vec<u8>> {
let ci_idx = ar.i_ci?;
let mut pos = StackIdx(0);
let name_owned: Option<Vec<u8>> = find_local(state, ci_idx, n, Some(&mut pos));
if name_owned.is_some() {
let val = state.get_at(state.top_idx() - 1).clone();
state.set_at(pos, val);
state.pop_n(1);
}
name_owned
}
fn func_info(ar: &mut LuaDebug, cl: Option<&LuaClosure>) {
if !is_lua_closure(cl) {
ar.source = Some(b"=[C]".to_vec());
ar.srclen = b"=[C]".len();
ar.linedefined = -1;
ar.lastlinedefined = -1;
ar.what = Some(b"C");
} else {
let lua_cl = match cl {
Some(LuaClosure::Lua(cl)) => cl,
_ => unreachable!(),
};
let proto: &LuaProto = &lua_cl.proto;
if let Some(src) = proto.source_string() {
ar.source = Some(src.as_bytes().to_vec());
ar.srclen = src.as_bytes().len();
} else {
ar.source = Some(b"=?".to_vec());
ar.srclen = b"=?".len();
}
ar.linedefined = proto.linedefined;
ar.lastlinedefined = proto.lastlinedefined;
ar.what = Some(if ar.linedefined == 0 { b"main" } else { b"Lua" });
}
chunk_id(&mut ar.short_src, ar.source.as_deref().unwrap_or(b"?"), ar.srclen);
}
fn next_line(p: &LuaProto, currentline: i32, pc: usize) -> i32 {
if p.lineinfo.get(pc).copied() != Some(ABS_LINE_INFO) {
currentline + p.lineinfo[pc] as i32
} else {
get_func_line(p, pc as i32)
}
}
fn collect_valid_lines(state: &mut LuaState, cl: Option<&LuaClosure>) -> Result<(), LuaError> {
if !is_lua_closure(cl) {
state.push(LuaValue::Nil);
return Ok(());
}
let lua_cl = match cl {
Some(LuaClosure::Lua(cl)) => cl.clone(),
_ => unreachable!(),
};
let proto: GcRef<LuaProto> = lua_cl.proto.clone();
let p: &LuaProto = &proto;
let mut currentline = p.linedefined;
let t = state.new_table();
state.push(LuaValue::Table(t.clone()));
if !p.lineinfo.is_empty() {
let v = LuaValue::Bool(true);
let start_i = if !p.is_vararg {
0usize
} else {
debug_assert!(
p.code.first().map(|i| i.is_vararg_prep()).unwrap_or(false),
"collect_valid_lines: first instruction of vararg should be OP_VARARGPREP"
);
currentline = next_line(p, currentline, 0);
1usize
};
for i in start_i..p.lineinfo.len() {
currentline = next_line(p, currentline, i);
t.raw_set_int(state, currentline as i64, v.clone())?;
}
}
Ok(())
}
fn get_func_name<'a>(
state: &'a LuaState,
ci: Option<&CallInfo>,
name: &mut Option<Vec<u8>>,
) -> Option<&'static [u8]> {
let ci = ci?;
if ci.callstatus & CIST_TAIL != 0 {
return None;
}
let prev_idx = ci.previous?;
let prev_ci = state.get_ci(prev_idx).clone();
funcname_from_call(state, &prev_ci, name)
}
fn aux_get_info(
state: &LuaState,
what: &[u8],
ar: &mut LuaDebug,
cl: Option<&LuaClosure>,
ci: Option<&CallInfo>,
) -> bool {
let mut status = true;
for &ch in what {
match ch {
b'S' => {
func_info(ar, cl);
}
b'l' => {
ar.currentline = match ci {
Some(ci) if ci.is_lua() => get_current_line(ci, state),
_ => -1,
};
}
b'u' => {
ar.nups = cl.map_or(0, |c| c.nupvalues() as u8);
match cl {
Some(LuaClosure::Lua(lua_cl)) => {
ar.isvararg = lua_cl.proto.is_vararg;
ar.nparams = lua_cl.proto.numparams;
}
_ => {
ar.isvararg = true;
ar.nparams = 0;
}
}
}
b't' => {
ar.istailcall = ci.map_or(false, |ci| ci.callstatus & CIST_TAIL != 0);
}
b'n' => {
let mut name: Option<Vec<u8>> = None;
ar.namewhat = get_func_name(state, ci, &mut name);
if ar.namewhat.is_none() {
ar.namewhat = Some(b"");
ar.name = None;
} else {
ar.name = name;
}
}
b'r' => match ci {
Some(ci) if ci.callstatus & CIST_TRAN != 0 => {
ar.ftransfer = ci.transfer_ftransfer();
ar.ntransfer = ci.transfer_ntransfer();
}
_ => {
ar.ftransfer = 0;
ar.ntransfer = 0;
}
},
b'L' | b'f' => {}
_ => {
status = false;
}
}
}
status
}
pub fn get_info(state: &mut LuaState, what: &[u8], ar: &mut LuaDebug) -> bool {
let (cl, ci_idx, func_val, what) = if what.first() == Some(&b'>') {
let func_val = state.peek_at(state.top_idx() - 1).clone();
state.pop_n(1);
debug_assert!(
matches!(func_val, LuaValue::Function(_)),
"get_info: function expected"
);
let cl = match &func_val {
LuaValue::Function(LuaClosure::Lua(_) | LuaClosure::C(_)) => {
Some(match &func_val {
LuaValue::Function(c) => c.clone(),
_ => unreachable!(),
})
}
_ => None,
};
(cl, None, func_val, &what[1..])
} else {
let ci_idx = match ar.i_ci { Some(i) => i, None => return false };
let func_val = state.get_at(state.get_ci(ci_idx).func).clone();
debug_assert!(
matches!(func_val, LuaValue::Function(_)),
"get_info: non-function at ci->func"
);
let cl = match &func_val {
LuaValue::Function(LuaClosure::Lua(_) | LuaClosure::C(_)) => {
Some(match &func_val {
LuaValue::Function(c) => c.clone(),
_ => unreachable!(),
})
}
_ => None,
};
(cl, Some(ci_idx), func_val, what)
};
let ci = ci_idx.and_then(|idx| Some(state.get_ci(idx).clone()));
let status = aux_get_info(state, what, ar, cl.as_ref(), ci.as_ref());
if what.contains(&b'f') {
state.push(func_val);
}
if what.contains(&b'L') {
let _ = collect_valid_lines(state, cl.as_ref());
}
status
}
#[inline]
fn filter_pc(pc: i32, jmptarget: i32) -> i32 {
if pc < jmptarget { -1 } else { pc }
}
fn find_set_reg(p: &LuaProto, lastpc: i32, reg: i32) -> i32 {
let mut setreg: i32 = -1;
let mut jmptarget: i32 = 0;
let effective_lastpc = if p.code.get(lastpc as usize).map_or(false, |i| i.is_mm_mode()) {
lastpc - 1
} else {
lastpc
};
for pc in 0..effective_lastpc {
let instr = p.code[pc as usize];
let op = instr.opcode();
let a = instr.arg_a() as i32;
let change = match op {
OpCode::LoadNil => {
let b = instr.arg_b() as i32;
a <= reg && reg <= a + b
}
OpCode::TForCall => reg >= a + 2,
OpCode::Call | OpCode::TailCall => reg >= a,
OpCode::Jmp => {
let b = instr.arg_s_j();
let dest = pc + 1 + b;
if dest <= effective_lastpc && dest > jmptarget {
jmptarget = dest;
}
false
}
_ => {
instr.test_a_mode() && reg == a
}
};
if change {
setreg = filter_pc(pc, jmptarget);
}
}
setreg
}
fn kname<'a>(p: &'a LuaProto, index: usize, name: &mut &'a [u8]) -> Option<&'static [u8]> {
match p.k.get(index) {
Some(LuaValue::Str(s)) => {
*name = s.as_bytes();
Some(b"constant")
}
_ => {
*name = b"?";
None
}
}
}
fn basic_get_obj_name<'a>(
p: &'a LuaProto,
ppc: &mut i32,
reg: i32,
name: &mut &'a [u8],
) -> Option<&'static [u8]> {
let pc = *ppc;
if let Some(local_name) = get_local_name(p, reg + 1, pc) {
*name = local_name;
return Some(b"local");
}
*ppc = find_set_reg(p, pc, reg);
let pc = *ppc;
if pc == -1 {
return None;
}
let instr = p.code[pc as usize];
let op = instr.opcode();
match op {
OpCode::Move => {
let b = instr.arg_b() as i32;
if b < instr.arg_a() as i32 {
return basic_get_obj_name(p, ppc, b, name);
}
}
OpCode::GetUpVal => {
*name = upval_name(p, instr.arg_b() as usize);
return Some(b"upvalue");
}
OpCode::LoadK => {
return kname(p, instr.arg_bx() as usize, name);
}
OpCode::LoadKx => {
let next = p.code[(pc + 1) as usize];
return kname(p, next.arg_ax() as usize, name);
}
_ => {}
}
None
}
fn rname<'a>(p: &'a LuaProto, pc: i32, c: i32, name: &mut &'a [u8]) {
let mut pc = pc;
let what = basic_get_obj_name(p, &mut pc, c, name);
if !matches!(what, Some(kind) if kind.first() == Some(&b'c')) {
*name = b"?";
}
}
fn rkname<'a>(p: &'a LuaProto, pc: i32, instr: Instruction, name: &mut &'a [u8]) {
let c = instr.arg_c() as i32;
if instr.arg_k() != 0 {
kname(p, c as usize, name);
} else {
rname(p, pc, c, name);
}
}
fn is_env<'a>(p: &'a LuaProto, pc: i32, instr: Instruction, isup: bool) -> &'static [u8] {
let t = instr.arg_b() as usize;
let mut name: &[u8] = b"?";
if isup {
name = upval_name(p, t);
} else {
let mut pc = pc;
basic_get_obj_name(p, &mut pc, t as i32, &mut name);
}
if name == LUA_ENV { b"global" } else { b"field" }
}
fn get_obj_name<'a>(
p: &'a LuaProto,
lastpc: i32,
reg: i32,
name: &mut &'a [u8],
) -> Option<&'static [u8]> {
let mut lastpc = lastpc;
let kind = basic_get_obj_name(p, &mut lastpc, reg, name);
if kind.is_some() {
return kind;
}
if lastpc == -1 {
return None;
}
let instr = p.code[lastpc as usize];
let op = instr.opcode();
match op {
OpCode::GetTabUp => {
let k = instr.arg_c() as usize;
kname(p, k, name);
Some(is_env(p, lastpc, instr, true))
}
OpCode::GetTable => {
let k = instr.arg_c() as i32;
rname(p, lastpc, k, name);
Some(is_env(p, lastpc, instr, false))
}
OpCode::GetI => {
*name = b"integer index";
Some(b"field")
}
OpCode::GetField => {
let k = instr.arg_c() as usize;
kname(p, k, name);
Some(is_env(p, lastpc, instr, false))
}
OpCode::Self_ => {
rkname(p, lastpc, instr, name);
Some(b"method")
}
_ => None,
}
}
fn funcname_from_code<'a>(
state: &LuaState,
p: &'a LuaProto,
pc: i32,
name: &mut Option<Vec<u8>>,
) -> Option<&'static [u8]> {
let instr = p.code[pc as usize];
let op = instr.opcode();
match op {
OpCode::Call | OpCode::TailCall => {
let mut name_bytes: &[u8] = b"?";
let kind = get_obj_name(p, pc, instr.arg_a() as i32, &mut name_bytes);
*name = Some(name_bytes.to_vec());
kind
}
OpCode::TForCall => {
*name = Some(b"for iterator".to_vec());
Some(b"for iterator")
}
OpCode::Self_ | OpCode::GetTabUp | OpCode::GetTable | OpCode::GetI | OpCode::GetField => {
get_tm_name(state, TagMethod::Index, name)
}
OpCode::SetTabUp | OpCode::SetTable | OpCode::SetI | OpCode::SetField => {
get_tm_name(state, TagMethod::NewIndex, name)
}
OpCode::MmBin | OpCode::MmBinI | OpCode::MmBinK => {
let tm_idx = instr.arg_c() as u8;
let tm = TagMethod::from_u8(tm_idx);
get_tm_name(state, tm, name)
}
OpCode::Unm => get_tm_name(state, TagMethod::Unm, name),
OpCode::BNot => get_tm_name(state, TagMethod::BNot, name),
OpCode::Len => get_tm_name(state, TagMethod::Len, name),
OpCode::Concat => get_tm_name(state, TagMethod::Concat, name),
OpCode::Eq => get_tm_name(state, TagMethod::Eq, name),
OpCode::Lt | OpCode::LtI | OpCode::GtI => get_tm_name(state, TagMethod::Lt, name),
OpCode::Le | OpCode::LeI | OpCode::GeI => get_tm_name(state, TagMethod::Le, name),
OpCode::Close | OpCode::Return => get_tm_name(state, TagMethod::Close, name),
_ => None,
}
}
fn get_tm_name(
state: &LuaState,
tm: TagMethod,
name: &mut Option<Vec<u8>>,
) -> Option<&'static [u8]> {
let raw_bytes: Vec<u8> = state.global()
.tm_name(tm)
.map(|s| s.as_bytes().to_vec())
.unwrap_or_default();
let stripped = raw_bytes
.strip_prefix(b"__")
.unwrap_or(&raw_bytes)
.to_vec();
*name = Some(stripped);
Some(b"metamethod")
}
fn funcname_from_call<'a>(
state: &'a LuaState,
ci: &CallInfo,
name: &mut Option<Vec<u8>>,
) -> Option<&'static [u8]> {
if ci.callstatus & CIST_HOOKED != 0 {
*name = Some(b"?".to_vec());
return Some(b"hook");
}
if ci.callstatus & CIST_FIN != 0 {
*name = Some(b"__gc".to_vec());
return Some(b"metamethod");
}
if ci.is_lua() {
let proto = ci_lua_proto(ci, state);
return funcname_from_code(state, &proto, current_pc(ci), name);
}
None
}
fn in_stack(ci: &CallInfo, val_idx: StackIdx, state: &LuaState) -> i32 {
let base = StackIdx(ci.func.0 + 1);
let ci_top = ci.top;
let mut pos = 0i32;
let mut cur = base;
while cur.0 < ci_top.0 {
if cur == val_idx {
return pos;
}
cur = StackIdx(cur.0 + 1);
pos += 1;
}
-1
}
fn get_upval_name<'a>(
ci: &CallInfo,
val_idx: StackIdx,
name: &mut &'a [u8],
state: &'a LuaState,
) -> Option<&'static [u8]> {
let proto = ci_lua_proto(ci, state);
let lua_cl = match state.get_at(ci.func) {
LuaValue::Function(LuaClosure::Lua(cl)) => cl.clone(),
_ => return None,
};
for (i, upval_slot) in lua_cl.upvals.iter().enumerate() {
let upval = upval_slot.get();
let state = upval.slot().clone();
if let lua_types::UpValState::Open { idx, .. } = state {
if idx == val_idx {
let _ = upval_name(&proto, i);
*name = b"upvalue";
return Some(b"upvalue");
}
}
}
None
}
fn format_var_info(kind: Option<&[u8]>, name: Option<&[u8]>) -> Vec<u8> {
match (kind, name) {
(Some(k), Some(n)) => {
let mut out = Vec::with_capacity(4 + k.len() + n.len());
out.extend_from_slice(b" (");
out.extend_from_slice(k);
out.extend_from_slice(b" '");
out.extend_from_slice(n);
out.extend_from_slice(b"')");
out
}
_ => Vec::new(),
}
}
fn var_info(state: &LuaState, val_idx: StackIdx) -> Vec<u8> {
let ci_idx = state.current_ci_idx();
let ci = state.get_ci(ci_idx).clone();
let mut kind: Option<&[u8]> = None;
let mut name_owned: Vec<u8> = b"?".to_vec();
if ci.is_lua() {
let mut up_name: &[u8] = b"?";
kind = get_upval_name(&ci, val_idx, &mut up_name, state);
if kind.is_some() {
name_owned = up_name.to_vec();
} else {
let reg = in_stack(&ci, val_idx, state);
if reg >= 0 {
let proto = ci_lua_proto(&ci, state);
let mut nref: &[u8] = b"?";
let pc = current_pc(&ci);
let k = get_obj_name(&proto, pc, reg, &mut nref);
kind = k;
if kind.is_some() {
name_owned = nref.to_vec();
}
}
}
}
format_var_info(kind, if kind.is_some() { Some(&name_owned) } else { None })
}
fn typeerror_inner(
state: &LuaState,
val: &LuaValue,
op: &[u8],
extra: &[u8],
) -> LuaError {
let t = state.obj_type_name(val);
let mut msg = Vec::new();
msg.extend_from_slice(b"attempt to ");
msg.extend_from_slice(op);
msg.extend_from_slice(b" a ");
msg.extend_from_slice(&t);
msg.extend_from_slice(b" value");
msg.extend_from_slice(extra);
prefixed_runtime(state, msg)
}
pub(crate) fn type_error(state: &LuaState, val: &LuaValue, val_idx: StackIdx, op: &[u8]) -> LuaError {
let extra = var_info(state, val_idx);
typeerror_inner(state, val, op, &extra)
}
pub(crate) fn type_error_with_hint(
state: &LuaState,
val: &LuaValue,
op: &[u8],
kind: &[u8],
name: &[u8],
) -> LuaError {
let extra = format_var_info(Some(kind), Some(name));
let t = obj_type_name_static(val);
let mut msg = Vec::new();
msg.extend_from_slice(b"attempt to ");
msg.extend_from_slice(op);
msg.extend_from_slice(b" a ");
msg.extend_from_slice(t);
msg.extend_from_slice(b" value");
msg.extend_from_slice(&extra);
prefixed_runtime(state, msg)
}
fn obj_type_name_static(val: &LuaValue) -> &'static [u8] {
match val {
LuaValue::Nil => b"nil",
LuaValue::Bool(_) => b"boolean",
LuaValue::Int(_) | LuaValue::Float(_) => b"number",
LuaValue::Str(_) => b"string",
LuaValue::Table(_) => b"table",
LuaValue::Function(_) => b"function",
LuaValue::UserData(_) => b"userdata",
LuaValue::LightUserData(_) => b"light userdata",
LuaValue::Thread(_) => b"thread",
}
}
pub(crate) fn call_error(state: &LuaState, val: &LuaValue, val_idx: StackIdx) -> LuaError {
let ci_idx = state.current_ci_idx();
let ci = state.get_ci(ci_idx).clone();
let mut name: Option<Vec<u8>> = None;
let kind = funcname_from_call(state, &ci, &mut name);
let extra = if kind.is_some() {
format_var_info(kind, name.as_deref())
} else {
var_info(state, val_idx)
};
typeerror_inner(state, val, b"call", &extra)
}
pub(crate) fn for_error(state: &mut LuaState, val: &LuaValue, what: &[u8]) -> LuaError {
let t = crate::tagmethods::obj_type_name(state, val)
.unwrap_or_else(|_| crate::tagmethods::type_name(val.base_type()).to_vec());
let mut msg = Vec::new();
msg.extend_from_slice(b"bad 'for' ");
msg.extend_from_slice(what);
msg.extend_from_slice(b" (number expected, got ");
msg.extend_from_slice(&t);
msg.push(b')');
prefixed_runtime(state, msg)
}
pub(crate) fn concat_error(
state: &LuaState,
p1: &LuaValue,
p1_idx: StackIdx,
p2: &LuaValue,
p2_idx: StackIdx,
) -> LuaError {
let (bad_val, bad_idx) = if matches!(p1, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_)) {
(p2, p2_idx)
} else {
(p1, p1_idx)
};
type_error(state, bad_val, bad_idx, b"concatenate")
}
pub(crate) fn op_int_error(
state: &LuaState,
p1: &LuaValue,
p1_idx: StackIdx,
p2: &LuaValue,
p2_idx: StackIdx,
msg: &[u8],
) -> LuaError {
let (bad_val, bad_idx) = if !matches!(p1, LuaValue::Int(_) | LuaValue::Float(_)) {
(p1, p1_idx)
} else {
(p2, p2_idx)
};
type_error(state, bad_val, bad_idx, msg)
}
pub(crate) fn to_int_error(
state: &LuaState,
p1: &LuaValue,
p1_idx: Option<StackIdx>,
_p2: &LuaValue,
p2_idx: Option<StackIdx>,
) -> LuaError {
let bad_idx = if p1.to_integer_no_strconv().is_none() {
p1_idx
} else {
p2_idx
};
let extra = match bad_idx {
Some(idx) => var_info(state, idx),
None => Vec::new(),
};
let mut msg = Vec::new();
msg.extend_from_slice(b"number");
msg.extend_from_slice(&extra);
msg.extend_from_slice(b" has no integer representation");
prefixed_runtime(state, msg)
}
pub(crate) fn order_error(state: &LuaState, p1: &LuaValue, p2: &LuaValue) -> LuaError {
let t1 = state.obj_type_name(p1);
let t2 = state.obj_type_name(p2);
let msg = if t1 == t2 {
let mut m = Vec::new();
m.extend_from_slice(b"attempt to compare two ");
m.extend_from_slice(&t1);
m.extend_from_slice(b" values");
m
} else {
let mut m = Vec::new();
m.extend_from_slice(b"attempt to compare ");
m.extend_from_slice(&t1);
m.extend_from_slice(b" with ");
m.extend_from_slice(&t2);
m
};
prefixed_runtime(state, msg)
}
pub(crate) fn add_info(
_state: Option<&mut LuaState>,
msg: &[u8],
src: Option<&LuaString>,
line: i32,
) -> Vec<u8> {
let mut buff = [0u8; LUA_IDSIZE];
if let Some(src) = src {
chunk_id(&mut buff, src.as_bytes(), src.len());
} else {
buff[0] = b'?';
}
let src_part = buff.iter().position(|&b| b == 0).map_or(&buff[..], |n| &buff[..n]);
let mut out = Vec::with_capacity(src_part.len() + 12 + msg.len());
out.extend_from_slice(src_part);
out.push(b':');
let line_str = line.to_string();
out.extend_from_slice(line_str.as_bytes());
out.extend_from_slice(b": ");
out.extend_from_slice(msg);
out
}
pub(crate) fn error_msg(state: &mut LuaState) -> Result<(), LuaError> {
if state.errfunc() != 0 {
let errfunc_idx = StackIdx(state.errfunc() as u32);
debug_assert!(
matches!(state.get_at(errfunc_idx), LuaValue::Function(_)),
"error_msg: error handler is not a function"
);
let arg = state.get_at(state.top_idx() - 1).clone();
state.push(arg);
let func = state.get_at(errfunc_idx).clone();
state.set_at(state.top_idx() - 2, func);
state.call_no_yield(state.top_idx() - 2, 1)?;
}
Err(runtime_from_top(state))
}
pub(crate) fn run_error(state: &mut LuaState, msg: Vec<u8>) -> Result<(), LuaError> {
state.gc().check_step();
let ci_idx = state.current_ci_idx();
let ci = state.get_ci(ci_idx).clone();
let final_msg = if ci.is_lua() {
let line = get_current_line(&ci, state);
let proto = ci_lua_proto(&ci, state);
let src = proto.source_string();
add_info(Some(state), &msg, src.map(|s| &**s), line)
} else {
msg
};
let str_val = state.new_string(&final_msg)?;
state.push(LuaValue::Str(str_val));
error_msg(state)
}
fn changed_line(p: &LuaProto, oldpc: i32, newpc: i32) -> bool {
if p.lineinfo.is_empty() {
return false;
}
if newpc - oldpc < MAX_IWTH_ABS / 2 {
let mut delta: i32 = 0;
let mut pc = oldpc;
loop {
pc += 1;
if pc as usize >= p.lineinfo.len() {
break;
}
let lineinfo = p.lineinfo[pc as usize];
if lineinfo == ABS_LINE_INFO {
break;
}
delta += lineinfo as i32;
if pc == newpc {
return delta != 0;
}
}
}
get_func_line(p, oldpc) != get_func_line(p, newpc)
}
pub(crate) fn trace_call(state: &mut LuaState) -> Result<i32, LuaError> {
let ci_idx = state.current_ci_idx();
let ci = state.get_ci(ci_idx).clone();
state.get_ci_mut(ci_idx).set_trap(true);
let proto = ci_lua_proto(&ci, state);
if ci.saved_pc() == 0 {
if proto.is_vararg {
return Ok(0);
} else if ci.callstatus & CIST_HOOKYIELD == 0 {
state.hook_call(ci_idx)?;
}
}
Ok(1)
}
pub(crate) fn trace_exec(state: &mut LuaState, pc: u32) -> Result<i32, LuaError> {
let ci_idx = state.current_ci_idx();
let ci = state.get_ci(ci_idx).clone();
let mask = state.hook_mask();
if !state.allowhook {
return Ok(1);
}
if mask & (LUA_MASKLINE | LUA_MASKCOUNT) == 0 {
state.get_ci_mut(ci_idx).set_trap(false);
return Ok(0);
}
let next_pc = pc + 1;
state.get_ci_mut(ci_idx).set_saved_pc(next_pc);
let counthook = if mask & LUA_MASKCOUNT != 0 {
let hc = state.hook_count() - 1;
state.set_hook_count(hc);
hc == 0
} else {
false
};
if counthook {
state.reset_hook_count();
} else if mask & LUA_MASKLINE == 0 {
return Ok(1);
}
if ci.callstatus & CIST_HOOKYIELD != 0 {
state.get_ci_mut(ci_idx).callstatus &= !CIST_HOOKYIELD;
return Ok(1);
}
if state.ci_lua_closure(ci_idx).is_none() {
return Ok(1);
}
let cur_instr = state.get_proto_instr(ci_idx, pc as u32);
if !cur_instr.is_in_top() {
let ci_top = state.get_ci(ci_idx).top;
state.set_top(ci_top);
}
if counthook {
state.call_hook_event(LUA_HOOKCOUNT, -1)?;
}
if mask & LUA_MASKLINE != 0 {
let proto = ci_lua_proto(&ci, state);
let oldpc = if state.old_pc() < proto.code.len() as u32 {
state.old_pc() as i32
} else {
0
};
let npci = next_pc as i32 - 1;
if npci <= oldpc || changed_line(&proto, oldpc, npci) {
let newline = get_func_line(&proto, npci);
state.call_hook_event(LUA_HOOKLINE, newline)?;
}
state.set_old_pc(npci as u32);
}
if state.status() == lua_types::status::LuaStatus::Yield {
if counthook {
state.set_hook_count(1);
}
state.get_ci_mut(ci_idx).callstatus |= CIST_HOOKYIELD;
return Err(LuaError::Yield);
}
Ok(1)
}
fn chunk_id(out: &mut [u8; LUA_IDSIZE], source: &[u8], _srclen: usize) {
out.fill(0);
let n = crate::object::chunk_id(&mut out[..], source);
if n < out.len() {
out[n] = 0;
}
}
fn get_local_name(p: &LuaProto, n: i32, pc: i32) -> Option<&[u8]> {
crate::func::get_local_name(p, n, pc)
}
fn get_local_name_from_closure(cl: &LuaClosureLua, n: i32, pc: i32) -> Option<&[u8]> {
get_local_name(&cl.proto, n, pc)
}
fn ci_lua_proto(ci: &CallInfo, state: &LuaState) -> GcRef<LuaProto> {
match state.get_at(ci.func) {
LuaValue::Function(LuaClosure::Lua(cl)) => cl.proto.clone(),
_ => panic!("ci_lua_proto: call frame does not hold a Lua closure"),
}
}