Files
moonwell-client-core/crates/benilla-lua/tests/replace.rs
T
Mathih13 d6655d8c10 benilla-lua: benilla's own 1.12 Lua VM, opt-in behind benilla-vm (#1254)
benilla-lua is a Lua 5.1.5 interpreter in Rust that speaks the 1.12.1 client's grammar. It is opt-in behind the `benilla-vm` feature, with `benilla-mlua`, the slice of mlua that benilla-ui uses, over it. The C build stays the default. Its front end ports 5.1.5's `lparser.c` and `lcode.c`, so registers, limits and messages match the C build's.

Our finish on top:
- the collector counts growth as C does, off the write path;
- frames under a leaky generic `for` start nil, as `luaD_precall`;
- the budget hook counts what C dispatches, and costs nothing when unset;
- `__index` chains stop at 100;
- mlua 0.11's conversions;
- `vm_parity` answers recorded per platform.

Tests: `vm_parity` on both VMs; the front-end corpus (8,543 chunks) against `string.dump`; 7,613 app and UI tests on benilla-lua. Built on macOS; Linux by CI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-16 16:16:09 +07:00

99 lines
3.0 KiB
Rust

//! A native holds `&mut Vm`, so safe code could put another VM or heap in place of the running
//! one, freeing the code and stack the running frames point into. The interpreter stops with a
//! panic there; it never runs on.
use benilla_lua::Vm;
fn replace_heap(vm: &mut Vm, _nargs: usize) -> benilla_lua::api::R<usize> {
vm.heap = Default::default();
Ok(0)
}
fn replace_vm(vm: &mut Vm, _nargs: usize) -> benilla_lua::api::R<usize> {
*vm = benilla_lua::new_state();
Ok(0)
}
fn run_with(native: benilla_lua::heap::NativeFn) -> String {
let mut vm = benilla_lua::new_state();
let f = vm.new_native("replace", native);
vm.set_global("replace", f);
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
// In a protected call, inside a Lua function, with a loop after: every way on would
// read the freed frames.
vm.exec(
b"local function g() replace() local s = 0 for i = 1, 10 do s = s + i end return s end \
return pcall(g)",
b"=t",
)
}));
match r {
Ok(_) => "ran on".to_owned(),
Err(p) => p
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| p.downcast_ref::<String>().cloned())
.unwrap_or_default(),
}
}
#[test]
fn a_native_that_replaces_the_heap_stops_the_vm() {
assert_eq!(
run_with(replace_heap),
"a native replaced the running Lua VM or its heap"
);
}
#[test]
fn a_native_that_replaces_the_vm_stops_it() {
assert_eq!(
run_with(replace_vm),
"a native replaced the running Lua VM or its heap"
);
}
/// A hook, and host code a collection inside Lua runs (a freed host closure's captures, dropped
/// through the host's `reentry`), hold the same `&mut Vm`; one that replaces it stops the VM too.
#[test]
fn a_hook_or_a_collection_that_replaces_the_vm_stops_it() {
use std::rc::Rc;
let stopped = |vm: &mut Vm, src: &[u8]| {
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| vm.exec(src, b"=t")));
r.err()
.and_then(|p| p.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_default()
};
let mut vm = benilla_lua::new_state();
vm.set_hook(
10,
Rc::new(|vm: &mut Vm| {
vm.heap = Default::default();
Ok(())
}),
);
assert_eq!(
stopped(
&mut vm,
b"local s = 0 for i = 1, 100 do s = s + i end return s"
),
"a native replaced the running Lua VM or its heap"
);
let mut vm = benilla_lua::new_state();
vm.reentry = Some(Rc::new(|vm: &mut Vm, f: &mut dyn FnMut()| {
*vm = benilla_lua::new_state();
f()
}));
let host: benilla_lua::heap::HostFn = Rc::new(|_: &mut Vm, _| Ok(0));
let f = vm.new_host("f", host);
vm.set_global("F", f);
assert_eq!(
stopped(
&mut vm,
b"F = nil for i = 1, 200000 do local t = {i} end return 1"
),
"a native replaced the running Lua VM or its heap"
);
}