use std::fmt::Write as _;
use crate::error::{LuaError, LuaResult, RuntimeError};
use crate::vm::callinfo::CallInfo;
use crate::vm::closure::Closure;
use crate::vm::debug_info;
use crate::vm::gc::arena::GcRef;
use crate::vm::state::{Gc, LuaState, MASK_CALL, MASK_COUNT, MASK_LINE, MASK_RET};
use crate::vm::table::Table;
use crate::vm::value::Val;
#[inline]
fn nargs(state: &LuaState) -> usize {
state.top.saturating_sub(state.base)
}
#[inline]
fn arg(state: &LuaState, n: usize) -> Val {
let idx = state.base + n;
if idx < state.top {
state.stack_get(idx)
} else {
Val::Nil
}
}
fn bad_argument(name: &str, n: usize, msg: &str) -> LuaError {
LuaError::Runtime(RuntimeError {
message: format!("bad argument #{n} to '{name}' ({msg})"),
level: 0,
traceback: vec![],
})
}
fn simple_error(msg: String) -> LuaError {
LuaError::Runtime(RuntimeError {
message: msg,
level: 0,
traceback: vec![],
})
}
fn check_number(state: &LuaState, name: &str, n: usize) -> LuaResult<f64> {
match arg(state, n) {
Val::Num(v) => Ok(v),
Val::Str(r) => {
let s = state
.gc
.string_arena
.get(r)
.map(|s| s.data().to_vec())
.ok_or_else(|| bad_argument(name, n + 1, "number expected"))?;
let text = String::from_utf8_lossy(&s);
text.trim()
.parse::<f64>()
.map_err(|_| bad_argument(name, n + 1, "number expected"))
}
_ => Err(bad_argument(name, n + 1, "number expected")),
}
}
fn get_thread_offset(state: &LuaState) -> usize {
usize::from(nargs(state) >= 1 && matches!(arg(state, 0), Val::Thread(_)))
}
pub(crate) use crate::error::chunkid;
pub(crate) enum StackLevel {
Real(usize),
TailCall,
}
pub(crate) fn resolve_stack_level(state: &LuaState, level: usize) -> Option<StackLevel> {
resolve_stack_level_raw(&state.stack, &state.call_stack, state.ci, &state.gc, level)
}
fn resolve_stack_level_raw(
stack: &[Val],
call_stack: &[CallInfo],
ci: usize,
_gc: &Gc,
level: usize,
) -> Option<StackLevel> {
let mut ci_idx = ci;
let mut remaining = level as i64;
while remaining > 0 && ci_idx > 0 {
remaining -= 1;
if call_stack[ci_idx].is_lua {
remaining -= i64::from(call_stack[ci_idx].tail_calls);
}
ci_idx -= 1;
}
if remaining == 0 && ci_idx > 0 {
Some(StackLevel::Real(ci_idx))
} else if remaining == 0 && ci_idx == 0 {
let func_slot = call_stack[0].func;
if func_slot < stack.len() && matches!(stack[func_slot], Val::Nil) {
Some(StackLevel::Real(0))
} else {
None
}
} else if remaining < 0 {
Some(StackLevel::TailCall)
} else {
None
}
}
pub(crate) fn current_line(state: &LuaState, ci_idx: usize) -> i32 {
let ci = &state.call_stack[ci_idx];
let func_val = state.stack_get(ci.func);
if let Val::Function(r) = func_val
&& let Some(Closure::Lua(lcl)) = state.gc.closures.get(r)
{
let pc = ci.saved_pc;
if pc > 0 && pc <= lcl.proto.line_info.len() {
return lcl.proto.line_info[pc - 1] as i32;
} else if !lcl.proto.line_info.is_empty() {
return lcl.proto.line_info[0] as i32;
}
}
-1
}
pub(crate) fn generate_traceback(state: &LuaState, msg: &str, start_level: usize) -> String {
generate_traceback_raw(
&state.stack,
&state.call_stack,
state.ci,
&state.gc,
msg,
start_level,
)
}
fn generate_traceback_raw(
stack: &[Val],
call_stack: &[CallInfo],
ci: usize,
gc: &Gc,
msg: &str,
start_level: usize,
) -> String {
const LEVELS1: usize = 12;
const LEVELS2: usize = 10;
let resolve = |level| resolve_stack_level_raw(stack, call_stack, ci, gc, level);
let mut result = String::new();
if !msg.is_empty() {
result.push_str(msg);
result.push('\n');
}
result.push_str("stack traceback:");
let mut level = start_level;
let mut first_part = true;
loop {
let Some(stack_level) = resolve(level) else {
break;
};
if level > LEVELS1 && first_part {
if resolve(level + LEVELS2).is_some() {
result.push_str("\n\t...");
let mut probe = level + LEVELS2;
while resolve(probe).is_some() {
probe += 1;
}
level = probe - LEVELS2;
first_part = false;
continue;
}
first_part = false;
}
result.push_str("\n\t");
match stack_level {
StackLevel::TailCall => {
result.push_str("(tail call): ?");
}
StackLevel::Real(ci_idx) => {
format_frame_line(&mut result, stack, call_stack, ci_idx, gc);
}
}
level += 1;
}
result
}
fn format_frame_line(
result: &mut String,
stack: &[Val],
call_stack: &[CallInfo],
ci_idx: usize,
gc: &Gc,
) {
let ci = &call_stack[ci_idx];
let func_val = if ci.func < stack.len() {
stack[ci.func]
} else {
Val::Nil
};
let func_name = debug_info::getfuncname_raw(call_stack, stack, gc, ci_idx, &gc.string_arena);
if let Val::Function(r) = func_val {
if let Some(cl) = gc.closures.get(r) {
match cl {
Closure::Lua(lcl) => {
let short_src = chunkid(&lcl.proto.source);
result.push_str(&short_src);
result.push(':');
let line = current_line_raw(call_stack, stack, gc, ci_idx);
if line > 0 {
let _ = write!(result, "{line}:");
}
if let Some((_kind, name)) = &func_name {
let _ = write!(result, " in function '{name}'");
} else if lcl.proto.line_defined == 0 {
result.push_str(" in main chunk");
} else {
let _ = write!(
result,
" in function <{}:{}>",
short_src, lcl.proto.line_defined
);
}
}
Closure::Rust(rcl) => {
result.push_str("[C]:");
if let Some((_kind, name)) = &func_name {
let _ = write!(result, " in function '{name}'");
} else if rcl.name.is_empty() {
result.push_str(" ?");
} else {
let _ = write!(result, " in function '{}'", rcl.name);
}
}
}
} else {
result.push_str("[C]: ?");
}
} else {
result.push_str("[C]: ?");
}
}
fn current_line_raw(call_stack: &[CallInfo], stack: &[Val], gc: &Gc, ci_idx: usize) -> i32 {
let ci = &call_stack[ci_idx];
let func_val = if ci.func < stack.len() {
stack[ci.func]
} else {
return -1;
};
if let Val::Function(r) = func_val
&& let Some(Closure::Lua(lcl)) = gc.closures.get(r)
{
let pc = ci.saved_pc;
if pc > 0 && pc <= lcl.proto.line_info.len() {
return lcl.proto.line_info[pc - 1] as i32;
} else if !lcl.proto.line_info.is_empty() {
return lcl.proto.line_info[0] as i32;
}
}
-1
}
fn get_local_name(state: &LuaState, ci_idx: usize, local_number: usize) -> Option<String> {
let ci = &state.call_stack[ci_idx];
let func_val = state.stack_get(ci.func);
if let Val::Function(r) = func_val
&& let Some(Closure::Lua(lcl)) = state.gc.closures.get(r)
{
let pc = if ci.saved_pc > 0 { ci.saved_pc - 1 } else { 0 };
let mut n = local_number;
for lv in &lcl.proto.local_vars {
if lv.start_pc as usize <= pc && pc < lv.end_pc as usize {
n -= 1;
if n == 0 {
return Some(lv.name.clone());
}
}
}
}
None
}
fn get_local_name_raw(
call_stack: &[CallInfo],
stack: &[Val],
gc: &Gc,
ci_idx: usize,
local_number: usize,
) -> Option<String> {
let ci = &call_stack[ci_idx];
let func_val = if ci.func < stack.len() {
stack[ci.func]
} else {
return None;
};
if let Val::Function(r) = func_val
&& let Some(Closure::Lua(lcl)) = gc.closures.get(r)
{
let pc = if ci.saved_pc > 0 { ci.saved_pc - 1 } else { 0 };
let mut n = local_number;
for lv in &lcl.proto.local_vars {
if lv.start_pc as usize <= pc && pc < lv.end_pc as usize {
n -= 1;
if n == 0 {
return Some(lv.name.clone());
}
}
}
}
None
}
pub fn db_getregistry(state: &mut LuaState) -> LuaResult<u32> {
state.push(Val::Table(state.registry));
Ok(1)
}
pub fn db_getmetatable(state: &mut LuaState) -> LuaResult<u32> {
if nargs(state) < 1 {
return Err(bad_argument("getmetatable", 1, "value expected"));
}
let val = arg(state, 0);
let mt = match val {
Val::Table(r) => state.gc.tables.get(r).and_then(Table::metatable),
Val::Userdata(r) => state
.gc
.userdata
.get(r)
.and_then(crate::vm::value::Userdata::metatable),
_ => {
let tag = crate::vm::metatable::type_tag(val);
state.gc.type_metatables[tag]
}
};
match mt {
Some(mt_ref) => state.push(Val::Table(mt_ref)),
None => state.push(Val::Nil),
}
Ok(1)
}
pub fn db_setmetatable(state: &mut LuaState) -> LuaResult<u32> {
if nargs(state) < 2 {
return Err(bad_argument("setmetatable", 2, "nil or table expected"));
}
let obj = arg(state, 0);
let mt_val = arg(state, 1);
let mt_ref = match mt_val {
Val::Nil => None,
Val::Table(r) => Some(r),
_ => return Err(bad_argument("setmetatable", 2, "nil or table expected")),
};
match obj {
Val::Table(r) => {
let t = state
.gc
.tables
.get_mut(r)
.ok_or_else(|| simple_error("table not found".into()))?;
t.set_metatable(mt_ref);
}
Val::Userdata(r) => {
let ud = state
.gc
.userdata
.get_mut(r)
.ok_or_else(|| simple_error("userdata not found".into()))?;
ud.set_metatable(mt_ref);
}
_ => {
let tag = crate::vm::metatable::type_tag(obj);
state.gc.type_metatables[tag] = mt_ref;
}
}
state.push(Val::Bool(true));
Ok(1)
}
pub fn db_getfenv(state: &mut LuaState) -> LuaResult<u32> {
if nargs(state) < 1 {
return Err(bad_argument("getfenv", 1, "value expected"));
}
let val = arg(state, 0);
let env = match val {
Val::Function(r) => state.gc.closures.get(r).map(|cl| match cl {
Closure::Lua(lcl) => lcl.env,
Closure::Rust(rcl) => rcl.env.unwrap_or(state.global),
}),
Val::Userdata(r) => {
let e = state
.gc
.userdata
.get(r)
.and_then(crate::vm::value::Userdata::env);
Some(e.unwrap_or(state.global))
}
Val::Thread(r) => {
if state.current_thread == Some(r) {
Some(state.global)
} else {
state.gc.threads.get(r).map(|t| t.global)
}
}
_ => Some(state.global),
};
state.push(Val::Table(env.unwrap_or(state.global)));
Ok(1)
}
pub fn db_setfenv(state: &mut LuaState) -> LuaResult<u32> {
if nargs(state) < 2 {
return Err(bad_argument("setfenv", 2, "table expected"));
}
let obj = arg(state, 0);
let Val::Table(new_env) = arg(state, 1) else {
return Err(bad_argument("setfenv", 2, "table expected"));
};
match obj {
Val::Function(r) => {
let cl = state.gc.closures.get_mut(r).ok_or_else(|| {
simple_error("'setfenv' cannot change environment of given object".into())
})?;
match cl {
Closure::Lua(lcl) => lcl.env = new_env,
Closure::Rust(rcl) => rcl.env = Some(new_env),
}
}
Val::Userdata(r) => {
let ud = state.gc.userdata.get_mut(r).ok_or_else(|| {
simple_error("'setfenv' cannot change environment of given object".into())
})?;
ud.set_env(Some(new_env));
}
Val::Thread(r) => {
if state.current_thread == Some(r) {
state.global = new_env;
} else if let Some(thread) = state.gc.threads.get_mut(r) {
thread.global = new_env;
} else {
return Err(simple_error(
"'setfenv' cannot change environment of given object".into(),
));
}
}
_ => {
return Err(simple_error(
"'setfenv' cannot change environment of given object".into(),
));
}
}
state.push(obj);
Ok(1)
}
struct ClosureInfo {
is_lua: bool,
source: String,
short_src: String,
line_defined: i64,
last_line_defined: i64,
what: &'static str,
nups: i64,
name: String,
line_info: Vec<u32>,
}
fn extract_closure_info(
state: &LuaState,
cl_ref: GcRef<crate::vm::closure::Closure>,
) -> Option<ClosureInfo> {
let cl = state.gc.closures.get(cl_ref)?;
Some(match cl {
Closure::Lua(lcl) => ClosureInfo {
is_lua: true,
source: lcl.proto.source.clone(),
short_src: chunkid(&lcl.proto.source),
line_defined: i64::from(lcl.proto.line_defined),
last_line_defined: i64::from(lcl.proto.last_line_defined),
what: if lcl.proto.line_defined == 0 {
"main"
} else {
"Lua"
},
nups: i64::from(lcl.proto.num_upvalues),
name: String::new(),
line_info: lcl.proto.line_info.clone(),
},
Closure::Rust(rcl) => ClosureInfo {
is_lua: false,
source: "=[C]".into(),
short_src: "[C]".into(),
line_defined: -1,
last_line_defined: -1,
what: "C",
nups: rcl.upvalues.len() as i64,
name: rcl.name.clone(),
line_info: Vec::new(),
},
})
}
pub fn db_getinfo(state: &mut LuaState) -> LuaResult<u32> {
let func_arg_idx = get_thread_offset(state);
let options = if nargs(state) > func_arg_idx + 1 {
match arg(state, func_arg_idx + 1) {
Val::Str(r) => state.gc.string_arena.get(r).map_or_else(
|| "flnSu".into(),
|s| String::from_utf8_lossy(s.data()).to_string(),
),
_ => "flnSu".into(),
}
} else {
"flnSu".into()
};
let func_val = arg(state, func_arg_idx);
let co_ref = if func_arg_idx == 1 {
if let Val::Thread(r) = arg(state, 0) {
Some(r)
} else {
None
}
} else {
None
};
let (ci_idx, closure_ref, is_tail_call) = match func_val {
Val::Num(n) => {
#[allow(clippy::cast_possible_truncation)]
let level = n as usize;
let resolved = if let Some(cr) = co_ref {
if let Some(thread) = state.gc.threads.get(cr) {
resolve_stack_level_raw(
&thread.stack,
&thread.call_stack,
thread.ci,
&state.gc,
level,
)
} else {
resolve_stack_level(state, level)
}
} else {
resolve_stack_level(state, level)
};
match resolved {
Some(StackLevel::Real(target)) => {
let func = if let Some(cr) = co_ref {
state.gc.threads.get(cr).map_or(Val::Nil, |t| {
let idx = t.call_stack[target].func;
if idx < t.stack.len() {
t.stack[idx]
} else {
Val::Nil
}
})
} else {
state.stack_get(state.call_stack[target].func)
};
let cl_ref = match func {
Val::Function(r) => Some(r),
_ => None,
};
(Some(target), cl_ref, false)
}
Some(StackLevel::TailCall) => (None, None, true),
None => {
state.push(Val::Nil);
return Ok(1);
}
}
}
Val::Function(r) => (None, Some(r), false),
_ => {
return Err(bad_argument(
"getinfo",
func_arg_idx + 1,
"function or level expected",
));
}
};
let result_table = state.gc.alloc_table(Table::new());
if is_tail_call {
fill_tail_call_info(state, result_table, &options)?;
state.push(Val::Table(result_table));
return Ok(1);
}
let info = closure_ref.and_then(|r| extract_closure_info(state, r));
if let Some(info) = &info {
if options.contains('S') {
set_table_str(state, result_table, "source", &info.source)?;
set_table_str(state, result_table, "short_src", &info.short_src)?;
set_table_int(state, result_table, "linedefined", info.line_defined)?;
set_table_int(
state,
result_table,
"lastlinedefined",
info.last_line_defined,
)?;
set_table_str(state, result_table, "what", info.what)?;
}
if options.contains('l') {
let line = ci_idx.map_or(-1, |ci_i| {
if let Some(cr) = co_ref {
state.gc.threads.get(cr).map_or(-1, |t| {
current_line_raw(&t.call_stack, &t.stack, &state.gc, ci_i)
})
} else {
current_line(state, ci_i)
}
});
set_table_int(state, result_table, "currentline", i64::from(line))?;
}
if options.contains('u') {
set_table_int(state, result_table, "nups", info.nups)?;
}
if options.contains('n') {
let (name, namewhat) = if let Some(ci) = ci_idx {
if ci > 0 {
debug_info::getfuncname(state, ci, &state.gc.string_arena).map_or_else(
|| (info.name.clone(), String::new()),
|(kind, n)| (n, kind.to_string()),
)
} else {
(info.name.clone(), String::new())
}
} else {
let namewhat = if info.name.is_empty() {
String::new()
} else {
"global".to_string()
};
(info.name.clone(), namewhat)
};
if name.is_empty() {
set_table_val(state, result_table, "name", Val::Nil)?;
} else {
set_table_str(state, result_table, "name", &name)?;
}
set_table_str(state, result_table, "namewhat", &namewhat)?;
}
if let (true, Some(cl_ref)) = (options.contains('f'), closure_ref) {
set_table_val(state, result_table, "func", Val::Function(cl_ref))?;
}
if options.contains('L') && info.is_lua {
let lines_table = state.gc.alloc_table(Table::new());
for &line in &info.line_info {
let lt = state
.gc
.tables
.get_mut(lines_table)
.ok_or_else(|| simple_error("lines table not found".into()))?;
lt.raw_set(
Val::Num(f64::from(line)),
Val::Bool(true),
&state.gc.string_arena,
)?;
}
set_table_val(state, result_table, "activelines", Val::Table(lines_table))?;
}
}
state.push(Val::Table(result_table));
Ok(1)
}
fn fill_tail_call_info(
state: &mut LuaState,
table_ref: GcRef<Table>,
options: &str,
) -> LuaResult<()> {
if options.contains('S') {
set_table_str(state, table_ref, "source", "=(tail call)")?;
set_table_str(state, table_ref, "short_src", "(tail call)")?;
set_table_int(state, table_ref, "linedefined", -1)?;
set_table_int(state, table_ref, "lastlinedefined", -1)?;
set_table_str(state, table_ref, "what", "tail")?;
}
if options.contains('l') {
set_table_int(state, table_ref, "currentline", -1)?;
}
if options.contains('u') {
set_table_int(state, table_ref, "nups", 0)?;
}
if options.contains('n') {
set_table_str(state, table_ref, "name", "")?;
set_table_str(state, table_ref, "namewhat", "")?;
}
if options.contains('f') {
set_table_val(state, table_ref, "func", Val::Nil)?;
}
Ok(())
}
fn set_table_str(
state: &mut LuaState,
table_ref: GcRef<Table>,
key: &str,
value: &str,
) -> LuaResult<()> {
let k = state.gc.intern_string(key.as_bytes());
let v = state.gc.intern_string(value.as_bytes());
let t = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| simple_error("table not found".into()))?;
t.raw_set(Val::Str(k), Val::Str(v), &state.gc.string_arena)?;
Ok(())
}
fn set_table_int(
state: &mut LuaState,
table_ref: GcRef<Table>,
key: &str,
value: i64,
) -> LuaResult<()> {
let k = state.gc.intern_string(key.as_bytes());
let t = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| simple_error("table not found".into()))?;
t.raw_set(Val::Str(k), Val::Num(value as f64), &state.gc.string_arena)?;
Ok(())
}
fn set_table_val(
state: &mut LuaState,
table_ref: GcRef<Table>,
key: &str,
value: Val,
) -> LuaResult<()> {
let k = state.gc.intern_string(key.as_bytes());
let t = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| simple_error("table not found".into()))?;
t.raw_set(Val::Str(k), value, &state.gc.string_arena)?;
Ok(())
}
pub fn db_getlocal(state: &mut LuaState) -> LuaResult<u32> {
let arg_offset = get_thread_offset(state);
let level = check_number(state, "getlocal", arg_offset)? as usize;
let local_idx = check_number(state, "getlocal", arg_offset + 1)? as usize;
let co_ref = if arg_offset == 1 {
if let Val::Thread(r) = arg(state, 0) {
Some(r)
} else {
None
}
} else {
None
};
let resolved = if let Some(cr) = co_ref {
if let Some(thread) = state.gc.threads.get(cr) {
resolve_stack_level_raw(
&thread.stack,
&thread.call_stack,
thread.ci,
&state.gc,
level,
)
} else {
resolve_stack_level(state, level)
}
} else {
resolve_stack_level(state, level)
};
let target_ci = match resolved {
Some(StackLevel::Real(ci)) => ci,
Some(StackLevel::TailCall) | None => {
state.push(Val::Nil);
return Ok(1);
}
};
if let Some(cr) = co_ref {
if let Some(thread) = state.gc.threads.get(cr) {
let name = get_local_name_raw(
&thread.call_stack,
&thread.stack,
&state.gc,
target_ci,
local_idx,
);
let ci_base = thread.call_stack[target_ci].base;
let stack_idx = ci_base + local_idx - 1;
let limit = if target_ci == thread.ci {
thread.top
} else if target_ci + 1 < thread.call_stack.len() {
thread.call_stack[target_ci + 1].func
} else {
thread.top
};
if let Some(name) = name {
let val = if stack_idx < thread.stack.len() {
thread.stack[stack_idx]
} else {
Val::Nil
};
let name_ref = state.gc.intern_string(name.as_bytes());
state.push(Val::Str(name_ref));
state.push(val);
return Ok(2);
} else if local_idx > 0 && stack_idx < limit {
let val = if stack_idx < thread.stack.len() {
thread.stack[stack_idx]
} else {
Val::Nil
};
let name_ref = state.gc.intern_string(b"(*temporary)");
state.push(Val::Str(name_ref));
state.push(val);
return Ok(2);
}
state.push(Val::Nil);
return Ok(1);
}
state.push(Val::Nil);
return Ok(1);
}
let name = get_local_name(state, target_ci, local_idx);
let ci_base = state.call_stack[target_ci].base;
let stack_idx = ci_base + local_idx - 1;
let limit = if target_ci == state.ci {
state.top
} else if target_ci + 1 < state.call_stack.len() {
state.call_stack[target_ci + 1].func
} else {
state.top
};
if let Some(name) = name {
let val = state.stack_get(stack_idx);
let name_ref = state.gc.intern_string(name.as_bytes());
state.push(Val::Str(name_ref));
state.push(val);
Ok(2)
} else if local_idx > 0 && stack_idx < limit {
let val = state.stack_get(stack_idx);
let name_ref = state.gc.intern_string(b"(*temporary)");
state.push(Val::Str(name_ref));
state.push(val);
Ok(2)
} else {
state.push(Val::Nil);
Ok(1)
}
}
pub fn db_setlocal(state: &mut LuaState) -> LuaResult<u32> {
let arg_offset = get_thread_offset(state);
let level = check_number(state, "setlocal", arg_offset)? as usize;
let local_idx = check_number(state, "setlocal", arg_offset + 1)? as usize;
let new_val = arg(state, arg_offset + 2);
let co_ref = if arg_offset == 1 {
if let Val::Thread(r) = arg(state, 0) {
Some(r)
} else {
None
}
} else {
None
};
let resolved = if let Some(cr) = co_ref {
if let Some(thread) = state.gc.threads.get(cr) {
resolve_stack_level_raw(
&thread.stack,
&thread.call_stack,
thread.ci,
&state.gc,
level,
)
} else {
resolve_stack_level(state, level)
}
} else {
resolve_stack_level(state, level)
};
let target_ci = match resolved {
Some(StackLevel::Real(ci)) => ci,
Some(StackLevel::TailCall) | None => {
return Ok(0);
}
};
if let Some(cr) = co_ref {
let info = state.gc.threads.get(cr).map(|thread| {
let name = get_local_name_raw(
&thread.call_stack,
&thread.stack,
&state.gc,
target_ci,
local_idx,
);
let ci_base = thread.call_stack[target_ci].base;
let stack_idx = ci_base + local_idx - 1;
let limit = if target_ci == thread.ci {
thread.top
} else if target_ci + 1 < thread.call_stack.len() {
thread.call_stack[target_ci + 1].func
} else {
thread.top
};
(name, stack_idx, limit)
});
if let Some((name, stack_idx, limit)) = info {
let name_ref = if let Some(name) = name {
if let Some(thread) = state.gc.threads.get_mut(cr)
&& stack_idx < thread.stack.len()
{
thread.stack[stack_idx] = new_val;
}
state.gc.intern_string(name.as_bytes())
} else if local_idx > 0 && stack_idx < limit {
if let Some(thread) = state.gc.threads.get_mut(cr)
&& stack_idx < thread.stack.len()
{
thread.stack[stack_idx] = new_val;
}
state.gc.intern_string(b"(*temporary)")
} else {
return Ok(0);
};
state.push(Val::Str(name_ref));
return Ok(1);
}
return Ok(0);
}
let name = get_local_name(state, target_ci, local_idx);
let ci_base = state.call_stack[target_ci].base;
let stack_idx = ci_base + local_idx - 1;
let limit = if target_ci == state.ci {
state.top
} else if target_ci + 1 < state.call_stack.len() {
state.call_stack[target_ci + 1].func
} else {
state.top
};
let name_ref = if let Some(name) = name {
if stack_idx < state.stack.len() {
state.stack[stack_idx] = new_val;
}
state.gc.intern_string(name.as_bytes())
} else if local_idx > 0 && stack_idx < limit {
if stack_idx < state.stack.len() {
state.stack[stack_idx] = new_val;
}
state.gc.intern_string(b"(*temporary)")
} else {
return Ok(0);
};
state.push(Val::Str(name_ref));
Ok(1)
}
pub fn db_getupvalue(state: &mut LuaState) -> LuaResult<u32> {
let Val::Function(cl_ref) = arg(state, 0) else {
return Err(bad_argument("getupvalue", 1, "function expected"));
};
let n = check_number(state, "getupvalue", 1)? as usize;
let cl = state
.gc
.closures
.get(cl_ref)
.ok_or_else(|| simple_error("closure not found".into()))?;
match cl {
Closure::Lua(lcl) => {
if n < 1 || n > lcl.upvalues.len() {
return Ok(0);
}
let uv_name = if n <= lcl.proto.upvalue_names.len() {
lcl.proto.upvalue_names[n - 1].clone()
} else {
String::new()
};
let uv_ref = lcl.upvalues[n - 1];
let uv_val = state
.gc
.upvalues
.get(uv_ref)
.map_or(Val::Nil, |uv| uv.get(&state.stack));
let name_ref = state.gc.intern_string(uv_name.as_bytes());
state.push(Val::Str(name_ref));
state.push(uv_val);
Ok(2)
}
Closure::Rust(_) => Ok(0),
}
}
pub fn db_setupvalue(state: &mut LuaState) -> LuaResult<u32> {
let Val::Function(cl_ref) = arg(state, 0) else {
return Err(bad_argument("setupvalue", 1, "function expected"));
};
let n = check_number(state, "setupvalue", 1)? as usize;
let new_val = arg(state, 2);
let is_lua = state.gc.closures.get(cl_ref).is_some_and(Closure::is_lua);
if !is_lua {
return Ok(0);
}
let (uv_name, uv_ref) = {
let cl = state
.gc
.closures
.get(cl_ref)
.ok_or_else(|| simple_error("closure not found".into()))?;
let lcl = cl
.as_lua()
.ok_or_else(|| simple_error("expected Lua closure".into()))?;
if n < 1 || n > lcl.upvalues.len() {
return Ok(0);
}
let name = if n <= lcl.proto.upvalue_names.len() {
lcl.proto.upvalue_names[n - 1].clone()
} else {
String::new()
};
(name, lcl.upvalues[n - 1])
};
if let Some(uv) = state.gc.upvalues.get_mut(uv_ref) {
uv.set(&mut state.stack, new_val);
}
let name_ref = state.gc.intern_string(uv_name.as_bytes());
state.push(Val::Str(name_ref));
Ok(1)
}
pub fn db_gethook(state: &mut LuaState) -> LuaResult<u32> {
let arg_offset = get_thread_offset(state);
let (hook_func, hook_mask, base_hook_count) = if arg_offset == 1 {
if let Val::Thread(co_ref) = arg(state, 0) {
if state.current_thread == Some(co_ref) {
(
state.hook.hook_func,
state.hook.hook_mask,
state.hook.base_hook_count,
)
} else if let Some(thread) = state.gc.threads.get(co_ref) {
(
thread.hook.hook_func,
thread.hook.hook_mask,
thread.hook.base_hook_count,
)
} else {
(Val::Nil, 0, 0)
}
} else {
(Val::Nil, 0, 0)
}
} else {
(
state.hook.hook_func,
state.hook.hook_mask,
state.hook.base_hook_count,
)
};
if hook_func.is_nil() || hook_mask == 0 {
state.push(Val::Nil);
} else {
state.push(hook_func);
}
let mut mask_str = String::with_capacity(3);
if hook_mask & MASK_CALL != 0 {
mask_str.push('c');
}
if hook_mask & MASK_RET != 0 {
mask_str.push('r');
}
if hook_mask & MASK_LINE != 0 {
mask_str.push('l');
}
let mask_ref = state.gc.intern_string(mask_str.as_bytes());
state.push(Val::Str(mask_ref));
#[allow(clippy::cast_precision_loss)]
state.push(Val::Num(f64::from(base_hook_count)));
Ok(3)
}
pub fn db_sethook(state: &mut LuaState) -> LuaResult<u32> {
let arg_offset = get_thread_offset(state);
let hook_arg = arg(state, arg_offset);
if hook_arg.is_nil() || nargs(state) <= arg_offset {
if arg_offset == 1 {
if let Val::Thread(co_ref) = arg(state, 0) {
if state.current_thread == Some(co_ref) {
state.hook.hook_func = Val::Nil;
state.hook.hook_mask = 0;
state.hook.base_hook_count = 0;
state.hook.hook_count = 0;
} else if let Some(thread) = state.gc.threads.get_mut(co_ref) {
thread.hook.hook_func = Val::Nil;
thread.hook.hook_mask = 0;
thread.hook.base_hook_count = 0;
thread.hook.hook_count = 0;
}
}
} else {
state.hook.hook_func = Val::Nil;
state.hook.hook_mask = 0;
state.hook.base_hook_count = 0;
state.hook.hook_count = 0;
}
return Ok(0);
}
let mask_str = match arg(state, arg_offset + 1) {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => return Err(bad_argument("sethook", arg_offset + 2, "string expected")),
};
#[allow(clippy::cast_possible_truncation)]
let count = if nargs(state) > arg_offset + 2 {
match arg(state, arg_offset + 2) {
Val::Num(n) => n as i32,
_ => 0,
}
} else {
0
};
if !matches!(hook_arg, Val::Function(_)) {
return Err(bad_argument("sethook", arg_offset + 1, "function expected"));
}
let mut mask: u8 = 0;
if mask_str.contains('c') {
mask |= MASK_CALL;
}
if mask_str.contains('r') {
mask |= MASK_RET;
}
if mask_str.contains('l') {
mask |= MASK_LINE;
}
if count > 0 {
mask |= MASK_COUNT;
}
if arg_offset == 1 {
if let Val::Thread(co_ref) = arg(state, 0) {
if state.current_thread == Some(co_ref) {
state.hook.hook_func = hook_arg;
state.hook.hook_mask = mask;
state.hook.base_hook_count = count;
state.hook.hook_count = count;
} else if let Some(thread) = state.gc.threads.get_mut(co_ref) {
thread.hook.hook_func = hook_arg;
thread.hook.hook_mask = mask;
thread.hook.base_hook_count = count;
thread.hook.hook_count = count;
}
}
} else {
state.hook.hook_func = hook_arg;
state.hook.hook_mask = mask;
state.hook.base_hook_count = count;
state.hook.hook_count = count;
}
Ok(0)
}
pub fn db_debug(state: &mut LuaState) -> LuaResult<u32> {
let _ = state;
Ok(0)
}
pub fn db_traceback(state: &mut LuaState) -> LuaResult<u32> {
let arg_offset = get_thread_offset(state);
#[allow(clippy::cast_possible_truncation)]
let start_level = if nargs(state) > arg_offset + 1 {
match arg(state, arg_offset + 1) {
Val::Num(n) => n as usize,
_ => 1,
}
} else {
usize::from(arg_offset == 0)
};
let msg: Option<String> = if nargs(state) <= arg_offset {
Some(String::new())
} else {
match arg(state, arg_offset) {
Val::Str(r) => {
let s = state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default();
Some(s)
}
Val::Num(n) => Some(format!("{}", Val::Num(n))),
other => {
state.push(other);
return Ok(1);
}
}
};
let msg_str = msg.as_deref().unwrap_or("");
let result = if arg_offset == 1 {
if let Val::Thread(co_ref) = arg(state, 0) {
if let Some(thread) = state.gc.threads.get(co_ref) {
generate_traceback_raw(
&thread.stack,
&thread.call_stack,
thread.ci,
&state.gc,
msg_str,
start_level,
)
} else {
generate_traceback(state, msg_str, start_level)
}
} else {
generate_traceback(state, msg_str, start_level)
}
} else {
generate_traceback(state, msg_str, start_level)
};
let result_ref = state.gc.intern_string(result.as_bytes());
state.push(Val::Str(result_ref));
Ok(1)
}