mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 02:12:00 +03:00
keep evaluateDocument's error boundary around tokenize/parse, not just evaluate
tokenize() and parseLine() ran outside the try/catch, and the parser has no recursion-depth guard for nested parens. A pathologically nested line overflowed the stack with an uncaught RangeError, killing the whole document instead of nulling just that line — the exact thing evaluateDocument is supposed to prevent.
This commit is contained in:
parent
aa29c12b37
commit
741ea16e40
2 changed files with 42 additions and 29 deletions
|
|
@ -282,3 +282,13 @@ describe("review fixes", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("regression 2026-08-12", () => {
|
||||
it("a deeply nested-parens line nulls out just that line, not the whole document", () => {
|
||||
const deep = "(".repeat(50000) + "1" + ")".repeat(50000);
|
||||
const r = eng.evaluateDocument(`1+1\n${deep}\n2+2`).map((x) => x.text);
|
||||
expect(r[0]).toBe("2");
|
||||
expect(r[1]).toBeNull();
|
||||
expect(r[2]).toBe("4");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { DateOrder, Decimal, EngineSettings, EvalError, Quantity, Unit, Value, XRefError, defaultSettings, qty } from "./types";
|
||||
import { Registry, buildRegistry, Completion } from "./registry";
|
||||
import { tokenize, Token } from "./tokenizer";
|
||||
import { parseLine } from "./parser";
|
||||
import { parseLine, ParsedLine } from "./parser";
|
||||
import { evaluate, EvalCtx, XRefResolver, toBase, fromBase } from "./evaluator";
|
||||
import { formatValue } from "./formatter";
|
||||
import { detectDateOrder } from "./datetime";
|
||||
|
|
@ -118,39 +118,42 @@ export class SumEngine {
|
|||
continue;
|
||||
}
|
||||
|
||||
const tokens = tokenize(line, this.reg, this.dateOrder);
|
||||
const knownVars = new Set(vars.keys());
|
||||
const parsed = parseLine(tokens, knownVars, line);
|
||||
|
||||
// tokenize/parseLine/evaluate all run under one try/catch: pathological
|
||||
// input (e.g. stack-overflowing nested parens in the parser) must null
|
||||
// out this one line, not throw past evaluateDocument and kill the rest.
|
||||
let tokens: Token[] = [];
|
||||
let parsed: ParsedLine = { expr: null };
|
||||
let value: Value | null = null;
|
||||
let lineError: string | undefined;
|
||||
if (parsed.expr) {
|
||||
const ctx: EvalCtx = {
|
||||
reg: this.reg,
|
||||
vars,
|
||||
line: { lineValues, lineKinds, index: i, lineText: rawLine },
|
||||
historicalRates: this.historicalRates,
|
||||
resolveXRef,
|
||||
};
|
||||
try {
|
||||
value = evaluate(parsed.expr, ctx);
|
||||
try {
|
||||
tokens = tokenize(line, this.reg, this.dateOrder);
|
||||
const knownVars = new Set(vars.keys());
|
||||
parsed = parseLine(tokens, knownVars, line);
|
||||
if (parsed.expr) {
|
||||
const ctx: EvalCtx = {
|
||||
reg: this.reg,
|
||||
vars,
|
||||
line: { lineValues, lineKinds, index: i, lineText: rawLine },
|
||||
historicalRates: this.historicalRates,
|
||||
resolveXRef,
|
||||
};
|
||||
// a line without a result beats a dead sheet.
|
||||
// surface unexpected bugs so the user knows why it failed
|
||||
} catch (e) {
|
||||
if (e instanceof XRefError) {
|
||||
lineError = e.message;
|
||||
} else if (e instanceof EvalError) {
|
||||
// Expected evaluation failure; remain silent
|
||||
} else {
|
||||
// Surface unexpected bugs/BigInt errors and log them
|
||||
lineError = e instanceof Error ? e.message : String(e);
|
||||
console.warn("evaluate failed:", e);
|
||||
}
|
||||
value = null;
|
||||
value = evaluate(parsed.expr, ctx);
|
||||
}
|
||||
if (value?.kind === "quantity" && !value.value.isFinite()) value = null;
|
||||
if (value && parsed.assign) vars.set(parsed.assign, value);
|
||||
} catch (e) {
|
||||
if (e instanceof XRefError) {
|
||||
lineError = e.message;
|
||||
} else if (e instanceof EvalError) {
|
||||
// Expected evaluation failure; remain silent
|
||||
} else {
|
||||
// Surface unexpected bugs/BigInt errors/RangeErrors and log them
|
||||
lineError = e instanceof Error ? e.message : String(e);
|
||||
console.warn("evaluate failed:", e);
|
||||
}
|
||||
value = null;
|
||||
}
|
||||
if (value?.kind === "quantity" && !value.value.isFinite()) value = null;
|
||||
if (value && parsed.assign) vars.set(parsed.assign, value);
|
||||
|
||||
lineValues.push(value);
|
||||
lineKinds.push("normal");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue