Skip to content

Commit 59a84fd

Browse files
alecthomasclaude
andcommitted
fix: bound the lexer mutator loop instead of a broken guard
The "saw mutator twice" guard could never fire: its seen map was declared after the restart label, so every goto reset it. It also cannot simply be hoisted, because splicing nested includes copies rules and legitimately reprocesses the same mutator pointer in other states. Replace it with an iteration bound, which also catches include cycles that previously span forever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 01c3eff commit 59a84fd

1 file changed

Lines changed: 6 additions & 5 deletions

File tree

regexp.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,16 +353,17 @@ func (r *RegexLexer) maybeCompile() (err error) {
353353
}
354354
}
355355
}
356+
// Mutators can't be tracked individually as include splicing duplicates rules.
357+
const mutatorLimit = 10000
358+
restarts := 0
356359
restart:
357-
seen := map[LexerMutator]bool{}
360+
if restarts++; restarts > mutatorLimit {
361+
return fmt.Errorf("lexer mutators did not converge after %d iterations; a LexerMutator may have failed to remove itself", mutatorLimit)
362+
}
358363
for state := range r.rules {
359364
for i := range len(r.rules[state]) {
360365
rule := r.rules[state][i]
361366
if compile, ok := rule.Mutator.(LexerMutator); ok {
362-
if seen[compile] {
363-
return fmt.Errorf("saw mutator %T twice; this should not happen", compile)
364-
}
365-
seen[compile] = true
366367
if err := compile.MutateLexer(r.rules, state, i); err != nil {
367368
return err
368369
}

0 commit comments

Comments
 (0)