use super::{
Arc, AtomicU64, Error, HOOK_BUDGET, HOOK_INTERVAL, HookTriggers, Lua, MultiValue, Ordering,
Result, Value, VmState,
};
pub(crate) fn harden(lua: &Lua) -> Result<()> {
let globals = lua.globals();
for name in [
"load",
"loadstring",
"dofile",
"loadfile",
"collectgarbage",
"require",
"getfenv",
"setfenv",
"rawget",
"rawset",
"rawequal",
"rawlen",
"print",
"warn",
] {
globals.set(name, Value::Nil).map_err(Error::lua)?;
}
lua.load(
r#"
local orig = table.concat
function table.concat(list, sep, i, j)
i = i or 1
j = j or #list
local parts = {}
for k = i, j do
local v = list[k]
local ty = type(v)
if ty == "string" or ty == "number" then
parts[#parts + 1] = v
elseif ty == "userdata" then
-- Fanout result objects (and any other userdata with __tostring).
-- mlua metatables are not readable via getmetatable, so type-gate here.
parts[#parts + 1] = tostring(v)
elseif v == nil then
error("invalid value (nil) at index " .. k .. " in table for 'concat'")
else
error("invalid value (" .. ty .. ") at index " .. k .. " in table for 'concat'")
end
end
return orig(parts, sep)
end
"#,
)
.exec()
.map_err(Error::lua)?;
Ok(())
}
pub(crate) fn install_instruction_budget(lua: &Lua) {
let fired = Arc::new(AtomicU64::new(0));
lua.set_hook(
HookTriggers::new().every_nth_instruction(HOOK_INTERVAL),
move |_lua, _debug| {
if crate::cancel::is_cancelled() {
return Err(mlua::Error::RuntimeError(
"lua execution cancelled".to_string(),
));
}
if fired.fetch_add(1, Ordering::Relaxed) >= HOOK_BUDGET {
return Err(mlua::Error::RuntimeError(
crate::error::lua_quota::INSTRUCTION.to_string(),
));
}
Ok(VmState::Continue)
},
);
}
pub(crate) fn value_to_string(value: &Value) -> Result<String> {
match value {
Value::String(s) => Ok(s.to_string_lossy()),
Value::Integer(i) => Ok(i.to_string()),
Value::Number(n) => Ok(n.to_string()),
Value::Boolean(b) => Ok(b.to_string()),
other => Err(Error::Lua(format!(
"cannot return a {} as a result",
other.type_name()
))),
}
}
pub(crate) fn scalar_return(returned: MultiValue) -> Result<Option<String>> {
match returned.into_iter().next() {
None | Some(Value::Nil) => Ok(None),
Some(value) => value_to_string(&value).map(Some),
}
}