[fix] Replace eval() with safe math expression parser#9263
[fix] Replace eval() with safe math expression parser#9263christian-byrne merged 2 commits intomainfrom
Conversation
Replace eval() in evaluateInput() with a custom recursive descent parser that only handles arithmetic (+, -, *, /, parentheses, decimals). Enable no-eval lint rule to prevent future usage. Fixes #8032
🎭 Playwright: ✅ 541 passed, 0 failed · 8 flaky📊 Browser Reports
|
🎨 Storybook: ✅ Built — View Storybook |
📝 WalkthroughWalkthroughReplaces runtime use of eval() with a new arithmetic parser and exposes evaluateInput; adds tests for the parser and widget evaluation; updates ESLint to disallow eval. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📦 Bundle: 4.46 MB gzip 🔴 +429 BDetailsSummary
Category Glance App Entry Points — 17.9 kB (baseline 17.9 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.02 MB (baseline 1.02 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 72.1 kB (baseline 72.1 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed Panels & Settings — 435 kB (baseline 435 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed User & Accounts — 16 kB (baseline 16 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed Editors & Dialogs — 736 B (baseline 736 B) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 1 added / 1 removed UI Components — 47.1 kB (baseline 47.1 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed Data & Services — 2.55 MB (baseline 2.55 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed Utilities & Hooks — 55.5 kB (baseline 55.5 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 11 added / 11 removed Vendor & Third-Party — 8.84 MB (baseline 8.84 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Other — 7.86 MB (baseline 7.86 MB) • 🔴 +2.43 kBBundles that do not match a named category
Status: 53 added / 53 removed |
⚡ Performance Report
Raw data{
"timestamp": "2026-02-27T03:36:19.733Z",
"gitSha": "97196ad689996968c5b9e7f57a0b31628819039e",
"branch": "fix/replace-eval-with-math-parser",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2004.1259999999852,
"styleRecalcs": 121,
"styleRecalcDurationMs": 19.762,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 388.805,
"heapDeltaBytes": -3178344
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2174.387999999965,
"styleRecalcs": 196,
"styleRecalcDurationMs": 65.492,
"layouts": 12,
"layoutDurationMs": 3.5820000000000003,
"taskDurationMs": 1162.085,
"heapDeltaBytes": -3864548
},
{
"name": "dom-widget-clipping",
"durationMs": 590.6239999999912,
"styleRecalcs": 46,
"styleRecalcDurationMs": 17.31,
"layouts": 1,
"layoutDurationMs": 0.25300000000000006,
"taskDurationMs": 378.611,
"heapDeltaBytes": 8308568
}
]
} |
- Rename unit() to primary() for clarity - Add modulo (%) operator support - Normalize negative zero to positive zero - Add depth limit (200) for nested parentheses - Use isFinite() instead of isNaN() to reject Infinity - Add tests for edge cases, unary-after-binary, modulo, depth limit, scientific/hex notation, and Infinity Fixes #9272 Fixes #9273 Fixes #9274 Fixes #9275
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/litegraph/src/utils/mathParser.test.ts (1)
77-83: Consider typing the complex expression test data for type safety.The
as numberassertion on line 82 is needed because TypeScript infersstring | numberfor the tuple. You could eliminate this by explicitly typing the test data array.🔧 Optional: Type the test data array
- test.each([ + test.each<[string, number]>([ ['2 /2+3 * 4.75- -6', 21.25], // ... other cases ])('complex expression: %s', (input, expected) => { - expect(evaluateMathExpression(input)).toBeCloseTo(expected as number) + expect(evaluateMathExpression(input)).toBeCloseTo(expected) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/litegraph/src/utils/mathParser.test.ts` around lines 77 - 83, The test data array for the parameterized test is currently inferred as (string | number) tuples forcing the use of `as number` in the assertion; explicitly annotate the test data as an array of string-number tuples (e.g., type it as [string, number][] or readonly [string, number][] depending on immutability) so the parameter `expected` is inferred as number and you can remove the `as number` cast in the expect call that uses evaluateMathExpression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/lib/litegraph/src/utils/mathParser.test.ts`:
- Around line 77-83: The test data array for the parameterized test is currently
inferred as (string | number) tuples forcing the use of `as number` in the
assertion; explicitly annotate the test data as an array of string-number tuples
(e.g., type it as [string, number][] or readonly [string, number][] depending on
immutability) so the parameter `expected` is inferred as number and you can
remove the `as number` cast in the expect call that uses evaluateMathExpression.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/lib/litegraph/src/utils/mathParser.test.tssrc/lib/litegraph/src/utils/mathParser.tssrc/lib/litegraph/src/utils/widget.test.tssrc/lib/litegraph/src/utils/widget.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/lib/litegraph/src/utils/widget.ts
- src/lib/litegraph/src/utils/mathParser.ts
- src/lib/litegraph/src/utils/widget.test.ts
## Summary Replace `eval()` in `evaluateInput()` with a custom recursive descent math parser, eliminating a security concern and enabling the `no-eval` lint rule. ## Changes - **New**: `mathParser.ts` — recursive descent parser for `+`, `-`, `*`, `/`, `%`, `()`, decimals, unary operators. Zero new dependencies. - **Modified**: `widget.ts` — replaced `eval()` call with `evaluateMathExpression()`, use `isFinite()` instead of `isNaN()` to reject `Infinity` - **Modified**: `.oxlintrc.json` — `no-eval` rule changed from `"off"` to `"error"` - **Tests**: 59 parser tests + 23 integration tests covering complex expressions, edge cases, and invalid input ## Review Feedback Addressed - Renamed `unit()` → `primary()` for clarity - Added modulo (`%`) operator support - Normalized negative zero to positive zero - Added depth limit (200) for nested parentheses - Used `isFinite()` instead of `isNaN()` to reject `Infinity`/`-Infinity` - Added tests for edge-case number formats, unary-after-binary operators, modulo, depth limits, scientific/hex notation, and `Infinity` Fixes #8032 Fixes #9272 Fixes #9273 Fixes #9274 Fixes #9275 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9263-fix-Replace-eval-with-safe-math-expression-parser-3136d73d3650812f9f8dea21d1ea4f06) by [Unito](https://www.unito.io)
|
1 similar comment
|
Replace `eval()` in `evaluateInput()` with a custom recursive descent math parser, eliminating a security concern and enabling the `no-eval` lint rule. - **New**: `mathParser.ts` — recursive descent parser for `+`, `-`, `*`, `/`, `%`, `()`, decimals, unary operators. Zero new dependencies. - **Modified**: `widget.ts` — replaced `eval()` call with `evaluateMathExpression()`, use `isFinite()` instead of `isNaN()` to reject `Infinity` - **Modified**: `.oxlintrc.json` — `no-eval` rule changed from `"off"` to `"error"` - **Tests**: 59 parser tests + 23 integration tests covering complex expressions, edge cases, and invalid input - Renamed `unit()` → `primary()` for clarity - Added modulo (`%`) operator support - Normalized negative zero to positive zero - Added depth limit (200) for nested parentheses - Used `isFinite()` instead of `isNaN()` to reject `Infinity`/`-Infinity` - Added tests for edge-case number formats, unary-after-binary operators, modulo, depth limits, scientific/hex notation, and `Infinity` Fixes #8032 Fixes #9272 Fixes #9273 Fixes #9274 Fixes #9275 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9263-fix-Replace-eval-with-safe-math-expression-parser-3136d73d3650812f9f8dea21d1ea4f06) by [Unito](https://www.unito.io)
…arser (#9263) (#9575) Backport of #9263 to core/1.40. Security fix — removes eval() usage. Conflicts resolved: added new exports to litegraph.ts barrel, added new test imports in widget.test.ts. **Original PR:** #9263 **Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9575-backport-core-1-40-fix-Replace-eval-with-safe-math-expression-parser-9263-31d6d73d365081099903f24d1d6584cc) by [Unito](https://www.unito.io) Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Summary
Replace
eval()inevaluateInput()with a custom recursive descent math parser, eliminating a security concern and enabling theno-evallint rule.Changes
mathParser.ts— recursive descent parser for+,-,*,/,%,(), decimals, unary operators. Zero new dependencies.widget.ts— replacedeval()call withevaluateMathExpression(), useisFinite()instead ofisNaN()to rejectInfinity.oxlintrc.json—no-evalrule changed from"off"to"error"Review Feedback Addressed
unit()→primary()for clarity%) operator supportisFinite()instead ofisNaN()to rejectInfinity/-InfinityInfinityFixes #8032
Fixes #9272
Fixes #9273
Fixes #9274
Fixes #9275
┆Issue is synchronized with this Notion page by Unito