Files
moonwell-client-core/crates/benilla-lua/tests/syntax_depth.rs
T
Sam Sung 826194e2ac benilla-lua's strings and numbers read and print as the client's (#1773)
benilla-lua's string and number library now answers as the client's:
- `string.byte` returns one byte (`0x7fc210`).
- `gsub` takes only a string or a function (`0x7fd163`), and `%0` raises (`0x7fce20`).
- `lower` and `upper` case-map UTF-8 (`0x7fbeb0`).
- `tonumber` with a base is MSVC's 32-bit `strtoul` (`0x40db09`), on every platform.
- Chunk names cut at a NUL and at `\n` (`0x835144`).
- `string.format` is the client's native one (`0x7fd3d0`): `__ftol` conversions, `%q`'s table, one-digit `n$`, and the client's errors.
- Number text is the MSVC CRT's, ported down to `$I10_OUTPUT`: `1.#INF`, `-1.#IND`, `1e+020`.
- A NaN made from numbers is the x87 indefinite, and the math library returns the runtime's own answers at infinities.

A fuzz against the client's own `sprintf`, run in emulation, matches 1.5M cases. Every fix fails its test without it. `tostring` and `%g` got faster.

Fixes #1367
Fixes #1368
Fixes #1373
Fixes #1375
Fixes #1376
Fixes #1450
Fixes #1481
Fixes #1486
Fixes #1770

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:51:19 +07:00

101 lines
3.6 KiB
Rust

//! Long and deep source on a 2 MB thread: a left-nested chain of any length compiles and runs,
//! as 5.1's parser reads it in a loop, and deep nesting stops at the client's level limit with its
//! message (or, for loops, at its limit on locals). Neither may overflow the Rust stack.
fn on_small_stack(f: impl FnOnce() + Send + 'static) {
std::thread::Builder::new()
.stack_size(2 << 20)
.spawn(f)
.unwrap()
.join()
.unwrap();
}
/// The chunk's first result, or its error.
fn run(src: String) -> String {
let mut vm = benilla_lua::new_state();
vm.exec(
b"function f() return f end m = { m = function(self) return self end }",
b"=setup",
)
.unwrap();
match vm.exec(src.as_bytes(), b"=t") {
Ok(v) => v.first().map_or("none".into(), |&v| vm.display(v)),
Err(e) => vm.display(e),
}
}
#[test]
fn left_nested_chains_compile_at_any_length() {
on_small_stack(|| {
let cases = [
(
format!("local x = f{} return type(x)", "()".repeat(30000)),
"function",
),
(
format!("local x = 1{} return x", " and 1".repeat(40000)),
"1",
),
(format!("return m{}", ":m()".repeat(20000)), "table"),
(format!("return 1{}", "+1".repeat(200000)), "200001"),
(
format!("return {}false", "false or ".repeat(49999)),
"false",
),
(
format!("local t = {{}} t.a = t return t{} == t", ".a".repeat(50000)),
"true",
),
(
format!("local x = 2 return x{}", "*x".repeat(30000)),
"1.#INF",
),
(format!("return 1 == 1{}", " == true".repeat(30000)), "true"),
];
for (src, want) in cases {
assert_eq!(run(src.clone()), want, "{}", &src[..40]);
}
});
}
#[test]
fn deep_nesting_stops_at_the_level_limit() {
on_small_stack(|| {
let n = 100000;
let cases = [
format!("return {}1{}", "(".repeat(n), ")".repeat(n)),
format!("return {}1", "not ".repeat(n)),
format!("return {}1", "- ".repeat(n)),
format!("return 1{}", " .. 1".repeat(n)),
format!("return 2{}", " ^ 2".repeat(n)),
format!("return {}1{}", "{".repeat(n), "}".repeat(n)),
format!("return {}1{}", "f(".repeat(n), ")".repeat(n)),
format!("return {}1{}", "m[".repeat(n), "]".repeat(n)),
format!("{}{}", "do ".repeat(n), "end ".repeat(n)),
format!("{}{}", "if x then ".repeat(n), "end ".repeat(n)),
format!("{}{}", "while x do ".repeat(n), "end ".repeat(n)),
format!("{}{}", "repeat ".repeat(n), "until x ".repeat(n)),
format!("{}{}", "for i = 1, 2 do ".repeat(n), "end ".repeat(n)),
format!("{}{}", "for k in x do ".repeat(n), "end ".repeat(n)),
format!(
"return {}1{}",
"function() return ".repeat(n),
" end".repeat(n)
),
format!("{}{}", "local function g() ".repeat(n), "end ".repeat(n)),
];
for src in cases {
let got = run(src.clone());
// Four locals a loop reach the 200 before the levels do.
let locals =
src.starts_with("for") && got.contains("too many local variables (limit=200)");
assert!(
got.contains("too many syntax levels near") || locals,
"{}: {got}",
&src[..40]
);
}
});
}