From c56bd10cb5ba192c76b6c5c6228625d58bd99892 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 16 Aug 2025 13:40:48 +0200 Subject: [PATCH] Fix undefined behavior in stalemate trap detection. An important part of it is a function which detects if we can move a king or pawn. If this function called before quiet move generation phase the end of the checked move list is undefined (Thanks to AliceRoselia pointing out we have some problem because of wrong bench in a test). This problem exists also in master but is really rarely triggered (thanks to vondele which found one) but it seen significant more often if the capture order score is increased. This fix now simply assumes in this case (and other similiar cases) that a king or pawn can move without checking any moves. Passed non-regression STC (with default book): LLR: 3.03 (-2.94,2.94) <-1.75,0.25> Total: 203008 W: 52465 L: 52424 D: 98119 Ptnml(0-2): 498, 20293, 59893, 20310, 510 https://tests.stockfishchess.org/tests/view/68a06ec5fd8719b088c8dc1a Passed non-regression STC (with stalemates book): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 31616 W: 14418 L: 14366 D: 2832 Ptnml(0-2): 1, 189, 15375, 243, 0 https://tests.stockfishchess.org/tests/view/68a07538fd8719b088c8dc1f closes https://github.com/official-stockfish/Stockfish/pull/6235 Bench: 2996176 --- src/movepick.cpp | 9 +++++++-- src/movepick.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index eb7c45837..dd6d71167 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -318,8 +318,13 @@ void MovePicker::skip_quiet_moves() { skipQuiets = true; } // this function must be called after all quiet moves and captures have been generated bool MovePicker::can_move_king_or_pawn() const { - // SEE negative captures shouldn't be returned in GOOD_CAPTURE stage - assert(stage > GOOD_CAPTURE && stage != EVASION_INIT); + + assert((GOOD_QUIET <= stage && stage <= BAD_QUIET) || stage == EVASION); + + // Until good capture state no quiet moves are generated for comparison so simply assume king or pawns can move. + // Do the same for other states that don't have a valid available move list. + if ((GOOD_QUIET > stage || stage > BAD_QUIET) && stage != EVASION) + return true; for (const ExtMove* m = moves; m < endGenerated; ++m) { diff --git a/src/movepick.h b/src/movepick.h index 9d6c02b0e..6a3305c6c 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -67,7 +67,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated; + ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated = moves; int stage; int threshold; Depth depth;