mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 01:02:01 +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 { DateOrder, Decimal, EngineSettings, EvalError, Quantity, Unit, Value, XRefError, defaultSettings, qty } from "./types";
|
||||||
import { Registry, buildRegistry, Completion } from "./registry";
|
import { Registry, buildRegistry, Completion } from "./registry";
|
||||||
import { tokenize, Token } from "./tokenizer";
|
import { tokenize, Token } from "./tokenizer";
|
||||||
import { parseLine } from "./parser";
|
import { parseLine, ParsedLine } from "./parser";
|
||||||
import { evaluate, EvalCtx, XRefResolver, toBase, fromBase } from "./evaluator";
|
import { evaluate, EvalCtx, XRefResolver, toBase, fromBase } from "./evaluator";
|
||||||
import { formatValue } from "./formatter";
|
import { formatValue } from "./formatter";
|
||||||
import { detectDateOrder } from "./datetime";
|
import { detectDateOrder } from "./datetime";
|
||||||
|
|
@ -118,12 +118,17 @@ export class SumEngine {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = tokenize(line, this.reg, this.dateOrder);
|
// tokenize/parseLine/evaluate all run under one try/catch: pathological
|
||||||
const knownVars = new Set(vars.keys());
|
// input (e.g. stack-overflowing nested parens in the parser) must null
|
||||||
const parsed = parseLine(tokens, knownVars, line);
|
// 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 value: Value | null = null;
|
||||||
let lineError: string | undefined;
|
let lineError: string | undefined;
|
||||||
|
try {
|
||||||
|
tokens = tokenize(line, this.reg, this.dateOrder);
|
||||||
|
const knownVars = new Set(vars.keys());
|
||||||
|
parsed = parseLine(tokens, knownVars, line);
|
||||||
if (parsed.expr) {
|
if (parsed.expr) {
|
||||||
const ctx: EvalCtx = {
|
const ctx: EvalCtx = {
|
||||||
reg: this.reg,
|
reg: this.reg,
|
||||||
|
|
@ -132,17 +137,16 @@ export class SumEngine {
|
||||||
historicalRates: this.historicalRates,
|
historicalRates: this.historicalRates,
|
||||||
resolveXRef,
|
resolveXRef,
|
||||||
};
|
};
|
||||||
try {
|
|
||||||
value = evaluate(parsed.expr, ctx);
|
|
||||||
// a line without a result beats a dead sheet.
|
// a line without a result beats a dead sheet.
|
||||||
// surface unexpected bugs so the user knows why it failed
|
value = evaluate(parsed.expr, ctx);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof XRefError) {
|
if (e instanceof XRefError) {
|
||||||
lineError = e.message;
|
lineError = e.message;
|
||||||
} else if (e instanceof EvalError) {
|
} else if (e instanceof EvalError) {
|
||||||
// Expected evaluation failure; remain silent
|
// Expected evaluation failure; remain silent
|
||||||
} else {
|
} else {
|
||||||
// Surface unexpected bugs/BigInt errors and log them
|
// Surface unexpected bugs/BigInt errors/RangeErrors and log them
|
||||||
lineError = e instanceof Error ? e.message : String(e);
|
lineError = e instanceof Error ? e.message : String(e);
|
||||||
console.warn("evaluate failed:", e);
|
console.warn("evaluate failed:", e);
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +154,6 @@ export class SumEngine {
|
||||||
}
|
}
|
||||||
if (value?.kind === "quantity" && !value.value.isFinite()) value = null;
|
if (value?.kind === "quantity" && !value.value.isFinite()) value = null;
|
||||||
if (value && parsed.assign) vars.set(parsed.assign, value);
|
if (value && parsed.assign) vars.set(parsed.assign, value);
|
||||||
}
|
|
||||||
|
|
||||||
lineValues.push(value);
|
lineValues.push(value);
|
||||||
lineKinds.push("normal");
|
lineKinds.push("normal");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue