use lua_types::{
LuaError, LuaType, LuaValue,
};
use lua_vm::state::{DynLibId, DynamicSymbol};
use crate::state_stub::{LuaState, LuaStateStubExt as _, lua_CFunction};
const LUA_POF: &[u8] = b"luaopen_";
const LUA_OFSEP: &[u8] = b"_";
const CLIBS: &[u8] = b"_CLIBS";
const LIB_FAIL_ABSENT: &[u8] = b"absent";
const LUA_PATH_SEP: u8 = b';';
const LUA_PATH_MARK: u8 = b'?';
const LUA_IGMARK: u8 = b'-';
#[cfg(target_os = "windows")]
const LUA_DIRSEP: u8 = b'\\';
#[cfg(not(target_os = "windows"))]
const LUA_DIRSEP: u8 = b'/';
const LUA_CSUBSEP: u8 = LUA_DIRSEP;
const LUA_LSUBSEP: u8 = LUA_DIRSEP;
const DLMSG: &[u8] = b"dynamic libraries not enabled; check your Lua installation";
const C_ABI_UNSUPPORTED_MSG: &[u8] =
b"dynamic library loaded, but Lua C ABI modules are not supported by this build";
const LUA_PATH_VAR: &[u8] = b"LUA_PATH";
const LUA_CPATH_VAR: &[u8] = b"LUA_CPATH";
#[cfg(not(target_os = "windows"))]
const LUA_PATH_DEFAULT: &[u8] = b"/usr/local/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?/init.lua;/usr/local/lib/lua/5.4/?.lua;/usr/local/lib/lua/5.4/?/init.lua;./?.lua;./?/init.lua";
#[cfg(target_os = "windows")]
const LUA_PATH_DEFAULT: &[u8] = b"./?.lua;./?/init.lua";
#[cfg(not(target_os = "windows"))]
const LUA_CPATH_DEFAULT: &[u8] =
b"/usr/local/lib/lua/5.4/?.so;/usr/local/lib/lua/5.4/loadall.so;./?.so";
#[cfg(target_os = "windows")]
const LUA_CPATH_DEFAULT: &[u8] = b"./?.dll";
const LUA_VERSUFFIX: &[u8] = b"_5_4";
fn getenv_bytes(state: &LuaState, name: &[u8]) -> Option<Vec<u8>> {
if let Some(env_fn) = state.global().env_hook {
return env_fn(name);
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
{
None
}
#[cfg(all(unix, not(all(target_arch = "wasm32", target_os = "unknown"))))]
{
use std::ffi::OsStr;
use std::os::unix::ffi::{OsStrExt, OsStringExt};
let os_name = OsStr::from_bytes(name);
std::env::var_os(os_name).map(|v| v.into_vec())
}
#[cfg(all(not(unix), not(all(target_arch = "wasm32", target_os = "unknown"))))]
{
std::str::from_utf8(name)
.ok()
.and_then(|name_str| std::env::var(name_str).ok())
.map(|s| s.into_bytes())
}
}
fn gsub_append(buf: &mut Vec<u8>, s: &[u8], pattern: &[u8], replacement: &[u8]) {
if pattern.is_empty() {
buf.extend_from_slice(s);
return;
}
let mut pos = 0;
while pos < s.len() {
if s[pos..].starts_with(pattern) {
buf.extend_from_slice(replacement);
pos += pattern.len();
} else {
buf.push(s[pos]);
pos += 1;
}
}
}
fn gsub_bytes(s: &[u8], pattern: &[u8], replacement: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
gsub_append(&mut out, s, pattern, replacement);
out
}
fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() {
return Some(0);
}
haystack.windows(needle.len()).position(|w| w == needle)
}
fn lsys_unloadlib(state: &mut LuaState, lib: DynLibId) {
if let Some(hook) = state.global().dynlib_unload_hook {
hook(lib);
}
}
fn lsys_load(
state: &mut LuaState,
path: &[u8],
see_glb: bool,
) -> (Option<DynLibId>, &'static [u8]) {
let hook = state.global().dynlib_load_hook;
let Some(load_fn) = hook else {
let s = match state.intern_str(DLMSG) {
Ok(s) => s,
Err(_) => return (None, LIB_FAIL_ABSENT),
};
state.push(LuaValue::Str(s));
return (None, LIB_FAIL_ABSENT);
};
match load_fn(state, path, see_glb) {
Ok(id) => (Some(id), b"open"),
Err(LuaError::File) => {
let mut msg = b"cannot find library '".to_vec();
msg.extend_from_slice(path);
msg.push(b'\'');
let s = match state.intern_str(&msg) {
Ok(s) => s,
Err(_) => return (None, LIB_FAIL_ABSENT),
};
state.push(LuaValue::Str(s));
(None, LIB_FAIL_ABSENT)
}
Err(err) => {
let msg = error_to_bytes(&err);
let s = match state.intern_str(&msg) {
Ok(s) => s,
Err(_) => return (None, b"open"),
};
state.push(LuaValue::Str(s));
(None, b"open")
}
}
}
fn lsys_sym(state: &mut LuaState, lib: DynLibId, sym: &[u8]) -> SymOutcome {
let hook = state.global().dynlib_symbol_hook;
let Some(sym_fn) = hook else {
let s = match state.intern_str(DLMSG) {
Ok(s) => s,
Err(_) => return SymOutcome::Missing,
};
state.push(LuaValue::Str(s));
return SymOutcome::Missing;
};
match sym_fn(state, lib, sym) {
Ok(DynamicSymbol::RustNative(f)) => SymOutcome::Found(f),
Ok(DynamicSymbol::LuaCAbi(_)) => {
let s = match state.intern_str(C_ABI_UNSUPPORTED_MSG) {
Ok(s) => s,
Err(_) => return SymOutcome::Missing,
};
state.push(LuaValue::Str(s));
SymOutcome::Missing
}
Ok(DynamicSymbol::Unsupported { reason }) => {
let s = match state.intern_str(&reason) {
Ok(s) => s,
Err(_) => return SymOutcome::Missing,
};
state.push(LuaValue::Str(s));
SymOutcome::Missing
}
Err(err) => {
let msg = error_to_bytes(&err);
let s = match state.intern_str(&msg) {
Ok(s) => s,
Err(_) => return SymOutcome::Missing,
};
state.push(LuaValue::Str(s));
SymOutcome::Missing
}
}
}
enum SymOutcome {
Found(lua_CFunction),
Missing,
}
fn error_to_bytes(e: &LuaError) -> Vec<u8> {
match e {
LuaError::Runtime(LuaValue::Str(s)) | LuaError::Syntax(LuaValue::Str(s)) => {
s.as_bytes().to_vec()
}
other => format!("{:?}", other).into_bytes(),
}
}
fn encode_dynlib_id(id: DynLibId) -> *mut std::ffi::c_void {
id.0 as usize as *mut std::ffi::c_void
}
fn decode_dynlib_id(p: *mut std::ffi::c_void) -> DynLibId {
DynLibId(p as usize as u64)
}
fn noenv(state: &mut LuaState) -> bool {
let _ = state.get_field_registry(b"LUA_NOENV");
let b = state.to_boolean(-1);
state.pop_n(1);
b
}
fn setpath(
state: &mut LuaState,
fieldname: &[u8],
envname: &[u8],
dft: &[u8],
) -> Result<(), LuaError> {
let mut nver = envname.to_vec();
nver.extend_from_slice(LUA_VERSUFFIX);
let path_opt = if noenv(state) {
None
} else {
getenv_bytes(state, &nver).or_else(|| getenv_bytes(state, envname))
};
let final_path: Vec<u8> = if path_opt.is_none() {
dft.to_vec()
} else {
let path = path_opt.unwrap();
let double_sep = [LUA_PATH_SEP, LUA_PATH_SEP];
if let Some(dftmark_pos) = find_subslice(&path, &double_sep) {
let mut buf = Vec::new();
if dftmark_pos > 0 {
buf.extend_from_slice(&path[..dftmark_pos]);
buf.push(LUA_PATH_SEP);
}
buf.extend_from_slice(dft);
let after = dftmark_pos + 2;
if after < path.len() {
buf.push(LUA_PATH_SEP);
buf.extend_from_slice(&path[after..]);
}
buf
} else {
path
}
};
let s = state.intern_str(&final_path)?;
state.push(LuaValue::Str(s));
state.set_field(-2, fieldname)?;
Ok(())
}
fn checkclib(state: &mut LuaState, path: &[u8]) -> Option<DynLibId> {
let _ = state.get_field_registry(CLIBS);
let _ = state.get_field(-1, path);
let handle = state.to_light_userdata(-1).map(decode_dynlib_id);
state.pop_n(2);
handle
}
fn addtoclib(state: &mut LuaState, path: &[u8], plib: DynLibId) -> Result<(), LuaError> {
state.get_field_registry(CLIBS)?;
state.push(LuaValue::LightUserData(encode_dynlib_id(plib)));
state.push_value(-1)?;
state.set_field(-3, path)?;
let n = state.len_at(-2);
state.raw_seti(-2, n + 1)?;
state.pop_n(1);
Ok(())
}
fn gctm(state: &mut LuaState) -> Result<usize, LuaError> {
let n = state.len_at(1);
let mut i = n;
while i >= 1 {
state.raw_geti(1, i)?;
if let Some(handle) = state.to_light_userdata(-1).map(decode_dynlib_id) {
lsys_unloadlib(state, handle);
}
state.pop_n(1);
i -= 1;
}
Ok(0)
}
enum LookForFuncStatus {
Ok,
ErrLib(&'static [u8]),
ErrFunc,
}
fn lookforfunc(
state: &mut LuaState,
path: &[u8],
sym: &[u8],
) -> Result<LookForFuncStatus, LuaError> {
let reg = match checkclib(state, path) {
Some(handle) => handle,
None => {
let (loaded, tag) = lsys_load(state, path, sym.first() == Some(&b'*'));
match loaded {
Some(handle) => {
addtoclib(state, path, handle)?;
handle
}
None => return Ok(LookForFuncStatus::ErrLib(tag)),
}
}
};
if sym.first() == Some(&b'*') {
state.push(LuaValue::Bool(true));
return Ok(LookForFuncStatus::Ok);
}
match lsys_sym(state, reg, sym) {
SymOutcome::Found(func) => {
state.push_c_function(func)?;
Ok(LookForFuncStatus::Ok)
}
SymOutcome::Missing => Ok(LookForFuncStatus::ErrFunc),
}
}
pub fn ll_loadlib(state: &mut LuaState) -> Result<usize, LuaError> {
let path = state.check_arg_string(1)?.to_vec();
let init = state.check_arg_string(2)?.to_vec();
let stat = lookforfunc(state, &path, &init)?;
let where_bytes: &[u8] = match stat {
LookForFuncStatus::Ok => return Ok(1),
LookForFuncStatus::ErrLib(tag) => tag,
LookForFuncStatus::ErrFunc => b"init",
};
state.push(LuaValue::Bool(false));
state.insert(-2)?;
let where_s = state.intern_str(where_bytes)?;
state.push(LuaValue::Str(where_s));
Ok(3)
}
fn readable(state: &LuaState, filename: &[u8]) -> bool {
match state.global().file_loader_hook {
Some(hook) => hook(filename).is_ok(),
None => false,
}
}
struct PathComponents<'a> {
remaining: &'a [u8],
}
impl<'a> PathComponents<'a> {
fn new(path: &'a [u8]) -> Self {
PathComponents { remaining: path }
}
}
impl<'a> Iterator for PathComponents<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.remaining.is_empty() {
return None;
}
let component = match self.remaining.iter().position(|&b| b == LUA_PATH_SEP) {
Some(sep_pos) => {
let c = &self.remaining[..sep_pos];
self.remaining = &self.remaining[sep_pos + 1..];
c
}
None => {
let c = self.remaining;
self.remaining = &[];
c
}
};
Some(component)
}
}
fn pusherrornotfound(state: &mut LuaState, path: &[u8]) -> Result<(), LuaError> {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(b"no file '");
gsub_append(&mut buf, path, &[LUA_PATH_SEP], b"'\n\tno file '");
buf.push(b'\'');
let s = state.intern_str(&buf)?;
state.push(LuaValue::Str(s));
Ok(())
}
fn searchpath(
state: &mut LuaState,
name: &[u8],
path: &[u8],
sep: &[u8],
dirsep: &[u8],
) -> Result<Option<Vec<u8>>, LuaError> {
let name_buf: Vec<u8> = if !sep.is_empty() && name.contains(&sep[0]) {
gsub_bytes(name, sep, dirsep)
} else {
name.to_vec()
};
let pathname: Vec<u8> = gsub_bytes(path, &[LUA_PATH_MARK], &name_buf);
for filename in PathComponents::new(&pathname) {
if readable(state, filename) {
let s = state.intern_str(filename)?;
state.push(LuaValue::Str(s));
return Ok(Some(filename.to_vec()));
}
}
pusherrornotfound(state, &pathname)?;
Ok(None)
}
pub fn ll_searchpath(state: &mut LuaState) -> Result<usize, LuaError> {
let name = state.check_arg_string(1)?.to_vec();
let path = state.check_arg_string(2)?.to_vec();
let sep = state.opt_arg_string(3, b".")?;
let dirsep_default = [LUA_DIRSEP];
let dirsep = state.opt_arg_string(4, &dirsep_default)?;
let found = searchpath(state, &name, &path, &sep, &dirsep)?;
if found.is_some() {
return Ok(1);
}
state.push(LuaValue::Bool(false));
state.insert(-2)?;
Ok(2)
}
fn findfile(state: &mut LuaState, name: &[u8], pname: &[u8], dirsep: u8) -> Result<Option<Vec<u8>>, LuaError> {
let uv = state.upvalue_index(1);
let _ = state.get_field(uv, pname);
let path_opt: Option<Vec<u8>> = state.to_bytes(-1);
let Some(path) = path_opt else {
state.pop_n(1);
return Err(LuaError::runtime(format_args!(
"'package.{}' must be a string",
String::from_utf8_lossy(pname)
)));
};
state.pop_n(1);
searchpath(state, name, &path, b".", &[dirsep])
}
fn checkload(state: &mut LuaState, stat: bool, filename: &[u8]) -> Result<usize, LuaError> {
if stat {
let s = state.intern_str(filename)?;
state.push(LuaValue::Str(s));
Ok(2)
} else {
let modname = state.to_bytes(1).unwrap_or_else(|| b"?".to_vec());
let loader_err = state.to_bytes(-1).unwrap_or_else(|| b"?".to_vec());
let mut msg = b"error loading module '".to_vec();
msg.extend_from_slice(&modname);
msg.extend_from_slice(b"' from file '");
msg.extend_from_slice(filename);
msg.extend_from_slice(b"':\n\t");
msg.extend_from_slice(&loader_err);
let s = state.intern_str(&msg)?;
return Err(LuaError::from_value(LuaValue::Str(s)));
}
}
fn searcher_lua(state: &mut LuaState) -> Result<usize, LuaError> {
let name = state.check_arg_string(1)?.to_vec();
let filename = findfile(state, &name, b"path", LUA_LSUBSEP)?;
if filename.is_none() {
return Ok(1);
}
let filename = filename.unwrap();
let chunk = match state.global().file_loader_hook {
Some(hook) => hook(&filename),
None => Err(LuaError::runtime(format_args!(
"no file_loader_hook registered; cannot read '{}'",
String::from_utf8_lossy(&filename)
))),
};
let load_ok = match chunk {
Ok(bytes) => {
let mut chunkname = b"@".to_vec();
chunkname.extend_from_slice(&filename);
match state.load(&bytes, &chunkname, None) {
Ok(true) => true,
Ok(false) => false,
Err(e) => {
let msg = match e {
LuaError::Syntax(LuaValue::Str(ref s))
| LuaError::Runtime(LuaValue::Str(ref s)) => s.as_bytes().to_vec(),
other => format!("{:?}", other).into_bytes(),
};
let s = state.intern_str(&msg)?;
state.push(LuaValue::Str(s));
false
}
}
}
Err(e) => {
let msg = match e {
LuaError::Runtime(LuaValue::Str(ref s)) => s.as_bytes().to_vec(),
other => format!("{:?}", other).into_bytes(),
};
let s = state.intern_str(&msg)?;
state.push(LuaValue::Str(s));
false
}
};
checkload(state, load_ok, &filename)
}
fn loadfunc(
state: &mut LuaState,
filename: &[u8],
modname: &[u8],
) -> Result<LookForFuncStatus, LuaError> {
let modname: Vec<u8> = gsub_bytes(modname, b".", LUA_OFSEP);
if let Some(mark_pos) = modname.iter().position(|&b| b == LUA_IGMARK) {
let prefix = &modname[..mark_pos];
let mut openfunc = LUA_POF.to_vec();
openfunc.extend_from_slice(prefix);
let stat = lookforfunc(state, filename, &openfunc)?;
if !matches!(stat, LookForFuncStatus::ErrFunc) {
return Ok(stat);
}
let tail = &modname[mark_pos + 1..];
let mut openfunc2 = LUA_POF.to_vec();
openfunc2.extend_from_slice(tail);
return lookforfunc(state, filename, &openfunc2);
}
let mut openfunc = LUA_POF.to_vec();
openfunc.extend_from_slice(&modname);
lookforfunc(state, filename, &openfunc)
}
fn searcher_c(state: &mut LuaState) -> Result<usize, LuaError> {
let name = state.check_arg_string(1)?.to_vec();
let filename = findfile(state, &name, b"cpath", LUA_CSUBSEP)?;
if filename.is_none() {
return Ok(1);
}
let filename = filename.unwrap();
let stat = loadfunc(state, &filename, &name)?;
let ok = matches!(stat, LookForFuncStatus::Ok);
checkload(state, ok, &filename)
}
fn searcher_croot(state: &mut LuaState) -> Result<usize, LuaError> {
let name = state.check_arg_string(1)?.to_vec();
let dot_pos = name.iter().position(|&b| b == b'.');
if dot_pos.is_none() {
return Ok(0);
}
let dot_pos = dot_pos.unwrap();
let root = &name[..dot_pos];
let root_s = state.intern_str(root)?;
state.push(LuaValue::Str(root_s));
let filename = findfile(state, root, b"cpath", LUA_CSUBSEP)?;
state.pop_n(1);
if filename.is_none() {
return Ok(1);
}
let filename = filename.unwrap();
let stat = loadfunc(state, &filename, &name)?;
match stat {
LookForFuncStatus::Ok => {}
LookForFuncStatus::ErrFunc => {
let mut msg = b"no module '".to_vec();
msg.extend_from_slice(&name);
msg.extend_from_slice(b"' in file '");
msg.extend_from_slice(&filename);
msg.push(b'\'');
let s = state.intern_str(&msg)?;
state.push(LuaValue::Str(s));
return Ok(1);
}
LookForFuncStatus::ErrLib(_) => {
return checkload(state, false, &filename);
}
}
let s = state.intern_str(&filename)?;
state.push(LuaValue::Str(s));
Ok(2)
}
fn searcher_preload(state: &mut LuaState) -> Result<usize, LuaError> {
let name = state.check_arg_string(1)?.to_vec();
state.get_field_registry(b"_PRELOAD")?;
let ty = state.get_field(-1, &name)?;
if ty == LuaType::Nil {
let mut msg = b"no field package.preload['".to_vec();
msg.extend_from_slice(&name);
msg.push(b'\'');
msg.push(b']');
let s = state.intern_str(&msg)?;
state.push(LuaValue::Str(s));
return Ok(1);
}
let tag = state.intern_str(b":preload:")?;
state.push(LuaValue::Str(tag));
Ok(2)
}
fn findloader(state: &mut LuaState, name: &[u8]) -> Result<(), LuaError> {
let uv = state.upvalue_index(1);
let ty = state.get_field(uv, b"searchers")?;
if ty != LuaType::Table {
return Err(LuaError::runtime(format_args!(
"'package.searchers' must be a table"
)));
}
let mut msg_buf: Vec<u8> = Vec::new();
let mut i: i64 = 1;
loop {
msg_buf.extend_from_slice(b"\n\t");
let item_ty = state.raw_geti(-1, i)?;
if item_ty == LuaType::Nil {
state.pop_n(1);
let len = msg_buf.len();
if len >= 2 {
msg_buf.truncate(len - 2);
}
let mut err = b"module '".to_vec();
err.extend_from_slice(name);
err.extend_from_slice(b"' not found:");
err.extend_from_slice(&msg_buf);
let err_s = state.intern_str(&err)?;
return Err(LuaError::from_value(LuaValue::Str(err_s)));
}
let name_s = state.intern_str(name)?;
state.push(LuaValue::Str(name_s));
state.call(1, 2)?;
if state.type_at(-2) == LuaType::Function {
return Ok(());
}
if state.type_at(-2) == LuaType::String {
state.pop_n(1);
if let Some(bytes) = state.to_bytes(-1) {
msg_buf.extend_from_slice(&bytes);
}
state.pop_n(1);
} else {
state.pop_n(2);
let len = msg_buf.len();
if len >= 2 {
msg_buf.truncate(len - 2);
}
}
i += 1;
}
}
pub fn ll_require(state: &mut LuaState) -> Result<usize, LuaError> {
let name = state.check_arg_string(1)?.to_vec();
lua_vm::api::set_top(state, 1)?;
state.get_field_registry(b"_LOADED")?;
state.get_field(2, &name)?;
if state.to_boolean(-1) {
return Ok(1);
}
state.pop_n(1);
findloader(state, &name)?;
state.rotate(-2, 1)?;
state.push_value(1)?;
state.push_value(-3)?;
state.call(2, 1)?;
if state.type_at(-1) != LuaType::Nil {
state.set_field(2, &name)?;
} else {
state.pop_n(1);
}
let ty = state.get_field(2, &name)?;
if ty == LuaType::Nil {
state.push(LuaValue::Bool(true));
state.copy_value(-1, -2)?;
state.set_field(2, &name)?;
}
state.rotate(-2, 1)?;
Ok(2)
}
fn createsearcherstable(state: &mut LuaState) -> Result<(), LuaError> {
let searchers: &[fn(&mut LuaState) -> Result<usize, LuaError>] = &[
searcher_preload,
searcher_lua,
searcher_c,
searcher_croot,
];
state.create_table(searchers.len() as i32, 0)?;
for (i, &f) in searchers.iter().enumerate() {
state.push_value(-2)?;
state.push_c_closure(f, 1)?;
state.raw_seti(-2, (i + 1) as i64)?;
}
state.set_field(-2, b"searchers")?;
Ok(())
}
fn createclibstable(state: &mut LuaState) -> Result<(), LuaError> {
state.get_subtable_registry(CLIBS)?;
state.create_table(0, 1)?;
state.push_c_function(gctm)?;
state.set_field(-2, b"__gc")?;
state.set_metatable(-2)?;
Ok(())
}
pub fn luaopen_package(state: &mut LuaState) -> Result<usize, LuaError> {
createclibstable(state)?;
state.new_lib(&[
(b"loadlib" as &[u8], ll_loadlib as fn(&mut LuaState) -> Result<usize, LuaError>),
(b"searchpath", ll_searchpath as fn(&mut LuaState) -> Result<usize, LuaError>),
])?;
createsearcherstable(state)?;
setpath(state, b"path", LUA_PATH_VAR, LUA_PATH_DEFAULT)?;
setpath(state, b"cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT)?;
let mut config: Vec<u8> = Vec::new();
config.push(LUA_DIRSEP);
config.push(b'\n');
config.push(LUA_PATH_SEP);
config.push(b'\n');
config.push(LUA_PATH_MARK);
config.push(b'\n');
config.push(b'!'); config.push(b'\n');
config.push(LUA_IGMARK);
config.push(b'\n');
let config_s = state.intern_str(&config)?;
state.push(LuaValue::Str(config_s));
state.set_field(-2, b"config")?;
state.get_subtable_registry(b"_LOADED")?;
state.set_field(-2, b"loaded")?;
state.get_subtable_registry(b"_PRELOAD")?;
state.set_field(-2, b"preload")?;
state.push_globals()?;
state.push_value(-2)?;
state.set_funcs_with_upvalues(
&[(b"require" as &[u8], ll_require as fn(&mut LuaState) -> Result<usize, LuaError>)],
1,
)?;
state.pop_n(1);
Ok(1)
}