Repository navigation
Conversation
|
I can confirm this fix resolves a real-world hang, independently of the A Svelte diff wedged a TUI (a Rust app on syntect 5.3.0 + two-face's onig dump) on a single 5-byte line, The grammar (bat's Only Both halves are needed. With only the I ported both onto the released v5.3.0 (master's Would be great to get this merged — it affects every syntect consumer that highlights Svelte. |
yxxbc
left a comment
There was a problem hiding this comment.
Verified against a real Svelte hang; both halves are required. See comment.
|
I believe this has been solved in a different way by #708, please check if that solution works for you or whether we still need this. |
Ran into an infinite spin using
grok buildon a React-to-Sveltejs port of something, and traced it to Syntect's parser entering an infinite loop on two kinds of cyclical grammar references.Examples:
contexts:
main:
- include: loop
loop:
- include: main
- match: '.*'
Explanation: Cyclic include. main includes loop, and loop includes main, so the include stack grows forever.
and
contexts:
main:
- match: '(?=.)'
set: main
- match: '.*'
Explanation: Zero-width set. (?=.) matches without consuming a character, then set: main returns to the same context. The cursor stays on the first byte and the scope-op list grows forever.
Causal root:
MatchIter follows includes by pushing the target context and walking its patterns. Nothing checks whether that context is already on the include stack, so a context that includes itself - directly, or through another context - pushes forever. The fix skips an include whose context pointer is already on ctx_stack.
The other loop is in parse_line. A match that does not consume input is fine in isolation (a lookahead push, for example). Upstream already bails out when a non-consuming pop happens at the same stack depth as an earlier non-consuming push. A non-consuming set however, or a push/pop whose depths do not line up, misses that check.
Coverage:
Tests cover a grammar whose contexts include each other, and one whose only rule is a zero-width set back to itself. Both used to hang inside parse_line.