Numeric literals above i64::MAX panic the parser instead of erroring
Summary
A numeric literal above i64::MAX panics the parser instead of returning a compile error. It
reproduces on any numeric filter field, in either mode:
type = Issue and weight > 9999999999999999999999999
# thread panicked at src/parser/literals.rs:29:44:
# called `Result::unwrap()` on an `Err` value: ParseIntError { kind: PosOverflow }This breaks the compile contract rather than just producing a bad message. compile() returns
{success: false, output: "Error: ..."} for every other kind of invalid input, but a panic
bypasses the unwrap_or_else in compile_glql entirely, so nothing serialises an error. In WASM
it surfaces as an unhandled runtime error instead of a structured failure the frontend can render.
Exactly i64::MAX (19 digits) parses fine - it is only the digit runs past it that panic.
Cause
src/parser/literals.rs:29:
// Parser for numbers
pub fn number(input: &str) -> IResult<&str, Value, GlqlError> {
map(digit1, |s: &str| Number(s.parse().unwrap()))(input)
}digit1 happily matches an unbounded run of digits, then s.parse::<i64>() returns
Err(ParseIntError { kind: PosOverflow }) and .unwrap() panics. Value::Number is an i64
(src/types/value.rs:11), so there is no representation for the value either - this is a genuine
domain limit, not just a missing conversion.
Options
- Return a parse error. Map the
Errinto aGlqlErrorso an out-of-range literal flows through the normal error contract like any other invalid token. This is the fix I would expect - it is local tonumber()and does not change any valid query's behaviour. - Saturate to
i64::MAX. Keeps the query compiling, but silently rewrites the user's predicate into one they did not ask for. Worse than erroring.
Option 1. Whichever way, it needs a test - the parser tests currently only cover Number(123)
(src/parser/literals.rs:108).
Also worth noting
digit1 also means negative literals never parse at all (weight > -5 fails as an unexpected
token). That is a separate question and may well be intended, but it is the same line of code, so
worth deciding at the same time.
Separately, there is a second, narrower boundary below this one: GraphQL's Int is 32-bit, so even
an in-i64 literal above i32::MAX cannot reach the wire as a valid Int. Sources that filter on
Int columns handle that themselves today (ai_usage_events clamps its featuresCount range args
for exactly this reason, which is correct for its purpose). Not asking for that to change - just
noting that if a range check ever lands at parse or analysis time, it would subsume the per-source
handling.
How it was found
Probing boundary values while reviewing !450 (merged), which clamps featuresCount range args to Int
bounds. The panic is unrelated to that MR - it sits one boundary above the clamp, at the i64
limit, and reproduces on weight, which long predates it.
Reproduction as a test:
#[test]
fn probe() {
// panics instead of returning an error
println!("{}", compile("weight > 9999999999999999999999999", r#"{"project":"g/p"}"#));
}