From 310cade6e5a97ba6223e400bc254b00b732e4977 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:32:47 +0200 Subject: [PATCH] Formula: print flat operator chains iteratively so deep trees cannot overflow the stack (#527) --- crates/formula/src/printer.rs | 18 ++++++++++++++---- crates/formula/src/tests.rs | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/formula/src/printer.rs b/crates/formula/src/printer.rs index a63599b..eb981ef 100644 --- a/crates/formula/src/printer.rs +++ b/crates/formula/src/printer.rs @@ -224,10 +224,20 @@ fn write_expr(s: &mut String, e: &Expr, r1c1: Option) { } }, }, - Expr::Binary(op, a, b) => { - write_expr(s, a, r1c1); - s.push_str(op.symbol()); - write_expr(s, b, r1c1); + Expr::Binary(..) => { + // Walk the left spine iteratively: a flat chain such as `1+1+1+...` parses into a + // tree one level deeper per operator, and recursing on it could exhaust the stack. + let mut rest: Vec<(&BinOp, &Expr)> = Vec::new(); + let mut first = e; + while let Expr::Binary(op, a, b) = first { + rest.push((op, b)); + first = a; + } + write_expr(s, first, r1c1); + for (op, right) in rest.into_iter().rev() { + s.push_str(op.symbol()); + write_expr(s, right, r1c1); + } } Expr::Call(name, args) => { s.push_str(name); diff --git a/crates/formula/src/tests.rs b/crates/formula/src/tests.rs index d3dd4c2..6faf202 100644 --- a/crates/formula/src/tests.rs +++ b/crates/formula/src/tests.rs @@ -301,3 +301,25 @@ fn trim_references_adjust() { let refs: Vec = parse("SUM(Sheet2!A1:.B5)").unwrap().references().iter().map(|r| printer::reference_a1(r)).collect(); assert_eq!(refs, ["Sheet2!A1:B5"]); } + +#[test] +fn prints_long_flat_chain_without_deep_recursion() { + // Small stack: a recursive printer overflows long before 20000 levels. + let handle = std::thread::Builder::new() + .stack_size(256 * 1024) + .spawn(|| { + let mut e = Expr::Number(1.0); + for _ in 0..20000 { + e = Expr::Binary(BinOp::Add, Box::new(e), Box::new(Expr::Number(1.0))); + } + let text = print(&e); + let r1c1 = print_r1c1(&e, gridcraft_core::CellRef { row: 0, col: 0 }); + // Dropping the deep tree recurses; leak it so only the printer is under test. + std::mem::forget(e); + (text, r1c1) + }) + .expect("spawn"); + let (text, r1c1) = handle.join().expect("printer must not overflow the stack"); + assert_eq!(text, vec!["1"; 20001].join("+")); + assert_eq!(r1c1, text); +}