fix: avoid catastrophic backtracking in number parsing for empty thousand separator (DEV-2120) - #1713
Conversation
✅ Deploy Preview for hyperformula-dev-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Task linked: HF-154 Agent-friendly docs |
6637a91 to
2f1ca3a
Compare
Performance comparison of head (6ad1637) vs base (d860eef) |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2f1ca3a. Configure here.
…sand separator (DEV-2120)
When thousandSeparator is empty (the default), the number-detection pattern's
group `(sep\d{3,})*` degenerated to `(\d{3,})*` adjacent to `\d+`, producing
catastrophic regex backtracking (ReDoS) on a long run of digits ending in a
non-digit character. Entering such a value into a cell froze the page.
Omit the thousand-separator group entirely when the separator is empty. Behavior
for non-empty separators is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2f1ca3a to
8ec26f4
Compare
|
|
||
| - Fixed the behavior of `MATCH`, `VLOOKUP`, `HLOOKUP`, and `XLOOKUP` functions when the search range contained empty cells. [#1697](https://github.com/handsontable/hyperformula/pull/1697) | ||
| - Fixed the `VLOOKUP`, `HLOOKUP`, and `XLOOKUP` functions to return `0` instead of an empty value when the matched cell in the result range is empty. [#1697](https://github.com/handsontable/hyperformula/pull/1697) | ||
| - Fixed the page freezing when entering a long string of digits ending in a non-digit character (e.g. `012...789a`) into a cell, caused by catastrophic regex backtracking in number parsing. [#1713](https://github.com/handsontable/hyperformula/pull/1713) |
There was a problem hiding this comment.
Reference this task in changelog
There was a problem hiding this comment.
Added in b136fec7a, wording widened in f022c9792 — the entry now cites #1520 next to the PR. Used the GitHub issue rather than the internal id, matching the rest of the file.
…os-number-parsing
The entry linked only the pull request. Add the reported bug it fixes, matching the issue-first citation convention used elsewhere in the file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…2120) The entry cited issue #1520 but described the trigger as digits "ending in" a non-digit character. In the reported repro the non-digit is followed by three more digits, so it sits near the end rather than at it. Widen the wording and add the reported shape as a second example. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1713 +/- ##
========================================
Coverage 97.21% 97.22%
========================================
Files 178 178
Lines 15611 15612 +1
Branches 3429 3430 +1
========================================
+ Hits 15177 15178 +1
Misses 426 426
Partials 8 8
🚀 New features to boost your workflow:
|

Summary
Entering a long run of digits ending in a non-digit character (e.g.
012345678901234567890123456789012345678901234567890123456789a) into a cell froze the page when formulas were enabled. Reported as #1520, where the non-digit character is a space. DEV-2120 / HOT-9767.Root cause
NumberLiteralHelperbuilds its number-detection pattern by interpolating the configured separators. With the defaultthousandSeparator: '', the group(${thousandSeparator}\d{3,})*degenerates to(\d{3,})*placed immediately after\d+:For a long digit run that ultimately fails to match (trailing non-digit), the engine explores exponentially many ways to partition the digits between
\d+and the repeated\d{3,}group — classic catastrophic backtracking (ReDoS). Parse time roughly doubles every ~2 characters, so a 60-character input never returns.The same pattern is reached from raw cell input (
CellContentParser) and from string→number coercion during formula evaluation (ArithmeticHelper), so=VALUE("…")and arithmetic over such text hung too. Fixing the pattern builder covers all entry points.Fix
Omit the thousand-separator group entirely when the separator is empty. The emitted pattern for a non-empty separator (
,,,.) is byte-for-byte unchanged — a literal separator is a mandatory anchor between repetitions, so no ambiguous partition exists and those configs were never vulnerable.Testing
Paired tests in handsontable/hyperformula-tests (branch
fix/dev-2120-redos-number-parsing):setCellContents(raw, percent, currency) and formula coercion (=VALUE(...));123) built throughbuildFromArraywith the sheet layout from the issue, including the dependent=SUM(A1,B1)formula.Reviewer notes
numberPattern.sourcehas no(\d{3,})*): a synchronous ReDoS cannot be caught by a Jest/Jasmine per-test timeout — the timer can't fire while the regex is stuck on the main thread — so asserting the emitted pattern shape is the one deterministic regression tripwire. The behavioral/e2e tests still cover actual behavior.NumberLiteralHelperchange reverted, the new#1520test hangs until killed (timeout 90→ exit 124); with the fix it finishes in ~20 ms. The default-config pattern goes from^([+-]?((\.\d+)|(\d+(\d{3,})*(\.\d*)?)))([eE][+-]?\d+)?$to^([+-]?((\.\d+)|(\d+(\.\d*)?)))([eE][+-]?\d+)?$.Notes
Branch brought up to date with
developby merge (not rebase) to preserve review history.Long-standing issue (reproduced on docs v17.1 and v18.0), not a v18 regression.
🤖 Generated with Claude Code
Note
Low Risk
Small, targeted regex construction change with unchanged behavior for non-empty thousand separators; low risk aside from edge cases in numeric string detection.
Overview
Fixes UI freezes when users enter a long digit string that fails number parsing (e.g. trailing letter or space before more digits), including the #1520 reproduction.
NumberLiteralHelperno longer emits the(\d{3,})*thousand-separator group whenthousandSeparatoris the default empty string. That degenerate pattern sat next to\d+and caused catastrophic backtracking on near-miss inputs; the same helper is used for raw cell parsing and formula coercion (VALUE, arithmetic), so one regex change covers those paths. Configs with a non-empty thousand separator keep the previous pattern shape.Reviewed by Cursor Bugbot for commit 6ad1637. Bugbot is set up for automated code reviews on this repo. Configure here.