Skip to content

Fix IndexError in read_game on an unmatched ) after an illegal move - #1204

Open
eeshsaxena wants to merge 1 commit into
niklasf:masterfrom
eeshsaxena:fix/pgn-variation-stack-underflow
Open

Fix IndexError in read_game on an unmatched ) after an illegal move#1204
eeshsaxena wants to merge 1 commit into
niklasf:masterfrom
eeshsaxena:fix/pgn-variation-stack-underflow

Conversation

@eeshsaxena

Copy link
Copy Markdown

Came across this feeding some scraped PGNs through read_game: a short game like

1. e4 Nf3 ) e5 *

raises IndexError: list index out of range from GameBuilder.visit_move instead of being parsed with the bad move recorded in game.errors.

What happens: Nf3 is illegal for Black, so the parser calls handle_error and sets skip_variation_depth = 1 to skip to the end of the (assumed) variation. The following ) closes that skip and calls end_variation(), but since the error was on the mainline there was never a matching begin_variation(). end_variation() pops variation_stack unconditionally, so it removes the root game node and empties the stack; the next move (e5) then does self.variation_stack[-1] and raises IndexError.

read_game already guards the normal ) path with len(board_stack) > 1, and begin_variation() asserts the root is never pushed as a variation, so I made end_variation() symmetric: it keeps the root on the stack. With that, the example parses to the e4 e5 mainline with one recorded error, and real variations are unaffected.

Extended test_variation_stack with this case (it sits right next to the existing superfluous-bracket cases). It raises IndexError on master and passes with the change; the full PGN test suite still passes. Found it by fuzzing read_game with mutated PGNs.

read_game recovers from an illegal move by entering skip mode, and a later
unmatched ')' closes that skip by calling end_variation(). When the error
happened on the mainline there was no matching begin_variation(), so the
unconditional variation_stack.pop() removed the root game node, leaving the
stack empty and making the next visit_move() raise IndexError. Keep the root
on the stack, mirroring the assert in begin_variation().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant