From 4f4cc84e8997f610fcb1b767769699d12ed2d252 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Wed, 10 May 2023 14:51:07 +0200 Subject: [PATCH 01/11] Add UCI handling of critical errors. --- src/uci.cpp | 8 ++++++++ src/uci.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/uci.cpp b/src/uci.cpp index 8f9684ee265..e246734fc6a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -395,4 +395,12 @@ Move UCI::to_move(const Position& pos, string& str) { return MOVE_NONE; } +void UCI::critical_error(const std::string& message) { + // Ideally we would report the error and continue, but UCI does not define such a situation nor + // gives any guarantees as to the program's behaviour for the user to rely on, + // so the safest option is to terminate. + sync_cout << "info string CRITICAL ERROR: " << message << '\n' << sync_endl; + std::terminate(); +} + } // namespace Stockfish diff --git a/src/uci.h b/src/uci.h index 9ca0ed36b4d..422e1c822b3 100644 --- a/src/uci.h +++ b/src/uci.h @@ -83,6 +83,8 @@ std::string pv(const Position& pos, Depth depth); std::string wdl(Value v, int ply); Move to_move(const Position& pos, std::string& str); +[[noreturn]] void critical_error(const std::string& message); + } // namespace UCI extern UCI::OptionsMap Options; From 7b40044ab84a506195f55806f0491f225e5fc557 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Wed, 10 May 2023 15:15:43 +0200 Subject: [PATCH 02/11] Terminate on (most) invalid positions. --- src/position.cpp | 83 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 2a9d798ff7d..ee9d1d36fdd 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -204,6 +204,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th ss >> std::noskipws; + int piece_count = 0; + // 1. Piece placement while ((ss >> token) && !isspace(token)) { @@ -214,13 +216,21 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th sq += 2 * SOUTH; else if ((idx = PieceToChar.find(token)) != string::npos) { + if (!is_ok(sq)) + UCI::critical_error("Invalid FEN. Tried to place a piece on square " + std::to_string(int(sq))); + if (++piece_count > 32) + UCI::critical_error("Invalid FEN. More than 32 pieces on the board."); put_piece(Piece(idx), sq); ++sq; } + else + UCI::critical_error(std::string("Invalid FEN. Invalid piece: ") + std::string(1, token)); } // 2. Active color ss >> token; + if (token != 'w' && token != 'b') + UCI::critical_error(std::string("Invalid FEN. Invalid side to move: ") + std::string(1, token)); sideToMove = (token == 'w' ? WHITE : BLACK); ss >> token; @@ -245,29 +255,40 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th else if (token >= 'A' && token <= 'H') rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + else if (token == '-') + break; else - continue; + UCI::critical_error(std::string("Invalid FEN. Expected castling rights. Got: ") + std::string(1, token)); set_castling_right(c, rsq); } + ss >> std::skipws; + // 4. En passant square. // Ignore if square is invalid or not on side to move relative rank 6. bool enpassant = false; - if ( ((ss >> col) && (col >= 'a' && col <= 'h')) - && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) + if (((ss >> col) && col != '-') && (ss >> row)) { - st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - - // En passant square will be considered only if - // a) side to move have a pawn threatening epSquare - // b) there is an enemy pawn in front of epSquare - // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + if ( (col >= 'a' && col <= 'h') + && (row == (sideToMove == WHITE ? '6' : '3'))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + } + else + { + UCI::critical_error("Invalid FEN. Invalid en-passant square."); + } } if (!enpassant) @@ -276,6 +297,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; + if (st->rule50 > 100) + UCI::critical_error("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); + // Convert from fullmove starting from 1 to gamePly starting from 0, // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); @@ -284,7 +308,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th thisThread = th; set_state(); - assert(pos_is_ok()); + if (!pos_is_ok()) + UCI::critical_error("Invalid FEN."); return *this; } @@ -1292,42 +1317,65 @@ bool Position::pos_is_ok() const { constexpr bool Fast = true; // Quick (default) or full check? + if ( pieceCount[W_KING] != 1 + || pieceCount[B_KING] != 1) + { + assert(0 && "pos_is_ok: King count"); + return false; + } + if ( (sideToMove != WHITE && sideToMove != BLACK) || piece_on(square(WHITE)) != W_KING || piece_on(square(BLACK)) != B_KING || ( ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6)) + { assert(0 && "pos_is_ok: Default"); + return false; + } if (Fast) return true; - if ( pieceCount[W_KING] != 1 - || pieceCount[B_KING] != 1 - || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + if (attackers_to(square(~sideToMove)) & pieces(sideToMove)) + { assert(0 && "pos_is_ok: Kings"); + return false; + } if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8) + { assert(0 && "pos_is_ok: Pawns"); + return false; + } if ( (pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces() || popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16) + { assert(0 && "pos_is_ok: Bitboards"); + return false; + } for (PieceType p1 = PAWN; p1 <= KING; ++p1) for (PieceType p2 = PAWN; p2 <= KING; ++p2) if (p1 != p2 && (pieces(p1) & pieces(p2))) + { assert(0 && "pos_is_ok: Bitboards"); + return false; + } for (Piece pc : Pieces) if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + { assert(0 && "pos_is_ok: Pieces"); + return false; + } for (Color c : { WHITE, BLACK }) for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) @@ -1338,7 +1386,10 @@ bool Position::pos_is_ok() const { if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) || castlingRightsMask[castlingRookSquare[cr]] != cr || (castlingRightsMask[square(c)] & cr) != cr) + { assert(0 && "pos_is_ok: Castling"); + return false; + } } return true; From c606f7d523ae46f961b57d10fcd4429d585e40d3 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Wed, 10 May 2023 15:15:53 +0200 Subject: [PATCH 03/11] Terminate on invalid UCI move sequence. --- src/uci.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/uci.cpp b/src/uci.cpp index e246734fc6a..e0548fe8666 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -71,8 +71,13 @@ namespace { pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse the move list, if any - while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + while (is >> token) { + m = UCI::to_move(pos, token); + + if (m == MOVE_NONE) + UCI::critical_error("Invalid moves. Illegal move."); + states->emplace_back(); pos.do_move(m, states->back()); } From a44292c173bad87f8e77ac5249d7224d67f8413c Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Wed, 10 May 2023 16:29:24 +0200 Subject: [PATCH 04/11] Terminate on positions with piece configurations impossible in chess that could result in too many generated moves. (for example 3 rooks and 9 queens) --- src/position.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/position.cpp b/src/position.cpp index ee9d1d36fdd..3ddc680e465 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -227,6 +227,26 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th UCI::critical_error(std::string("Invalid FEN. Invalid piece: ") + std::string(1, token)); } + int pawns_w = count(WHITE); + int pawns_b = count(BLACK); + if (pawns_w > 8) + UCI::critical_error("Invalid FEN. WHITE has more than 8 pawns."); + if (pawns_b > 8) + UCI::critical_error("Invalid FEN. BLACK has more than 8 pawns."); + + int additional_knights_w = std::max((int)count(WHITE) - 2, 0); + int additional_knights_b = std::max((int)count(BLACK) - 2, 0); + int additional_bishops_w = std::max((int)count(WHITE) - 2, 0); + int additional_bishops_b = std::max((int)count(BLACK) - 2, 0); + int additional_rooks_w = std::max((int)count(WHITE) - 2, 0); + int additional_rooks_b = std::max((int)count(BLACK) - 2, 0); + int additional_queens_w = std::max((int)count(WHITE) - 1, 0); + int additional_queens_b = std::max((int)count(BLACK) - 1, 0); + if (additional_knights_w + additional_bishops_w + additional_rooks_w + additional_queens_w > 8 - pawns_w) + UCI::critical_error("Invalid FEN. Invalid piece configuration for WHITE."); + if (additional_knights_b + additional_bishops_b + additional_rooks_b + additional_queens_b > 8 - pawns_b) + UCI::critical_error("Invalid FEN. Invalid piece configuration for BLACK."); + // 2. Active color ss >> token; if (token != 'w' && token != 'b') From c968e3be553423f4d170e13f45855a78c49dd3bf Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Fri, 12 May 2023 10:43:56 +0200 Subject: [PATCH 05/11] Overall improvements to the FEN parsing codeflow, validation, style. --- src/position.cpp | 199 +++++++++++++++++++++++++++++++---------------- src/uci.cpp | 5 +- 2 files changed, 136 insertions(+), 68 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 3ddc680e465..a1bea9feee6 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -193,9 +193,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th incremented after Black's move. */ - unsigned char col, row, token; - size_t idx; - Square sq = SQ_A8; + unsigned char token; std::istringstream ss(fenStr); std::memset(this, 0, sizeof(Position)); @@ -205,62 +203,111 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th ss >> std::noskipws; int piece_count = 0; + File file = FILE_A; + Rank rank = RANK_8; // 1. Piece placement - while ((ss >> token) && !isspace(token)) + for (;;) { - if (isdigit(token)) - sq += (token - '0') * EAST; // Advance the given number of files + if (!(ss >> token)) + UCI::critical_error("Invalid FEN. Unexpected end of stream."); + + if (isspace(token)) + break; + if (isdigit(token)) + { + const int diff = token - '0'; + if (diff < 1 || diff > 8) + UCI::critical_error("Invalid FEN. Invalid number of squares to skip."); + file = File(file + diff); + if (file > FILE_NB) + UCI::critical_error("Invalid FEN. Invalid file reached."); + } else if (token == '/') - sq += 2 * SOUTH; + { + if (file != FILE_NB) + UCI::critical_error("Invalid FEN. Trying to end rank when not at the end of it."); + + --rank; + file = FILE_A; - else if ((idx = PieceToChar.find(token)) != string::npos) { - if (!is_ok(sq)) - UCI::critical_error("Invalid FEN. Tried to place a piece on square " + std::to_string(int(sq))); + if (rank < RANK_1) + UCI::critical_error("Invalid FEN. Invalid rank reached."); + } + else + { + const size_t idx = PieceToChar.find(token); + if (idx == string::npos) + UCI::critical_error(std::string("Invalid FEN. Invalid piece: ") + std::string(1, token)); if (++piece_count > 32) - UCI::critical_error("Invalid FEN. More than 32 pieces on the board."); + UCI::critical_error("Invalid FEN. More than 32 pieces on the board."); + + const Square sq = make_square(file, rank); put_piece(Piece(idx), sq); - ++sq; + + ++file; + if (file > FILE_NB) + UCI::critical_error("Invalid FEN. Invalid file reached."); } - else - UCI::critical_error(std::string("Invalid FEN. Invalid piece: ") + std::string(1, token)); } - int pawns_w = count(WHITE); - int pawns_b = count(BLACK); - if (pawns_w > 8) - UCI::critical_error("Invalid FEN. WHITE has more than 8 pawns."); - if (pawns_b > 8) - UCI::critical_error("Invalid FEN. BLACK has more than 8 pawns."); - - int additional_knights_w = std::max((int)count(WHITE) - 2, 0); - int additional_knights_b = std::max((int)count(BLACK) - 2, 0); - int additional_bishops_w = std::max((int)count(WHITE) - 2, 0); - int additional_bishops_b = std::max((int)count(BLACK) - 2, 0); - int additional_rooks_w = std::max((int)count(WHITE) - 2, 0); - int additional_rooks_b = std::max((int)count(BLACK) - 2, 0); - int additional_queens_w = std::max((int)count(WHITE) - 1, 0); - int additional_queens_b = std::max((int)count(BLACK) - 1, 0); - if (additional_knights_w + additional_bishops_w + additional_rooks_w + additional_queens_w > 8 - pawns_w) - UCI::critical_error("Invalid FEN. Invalid piece configuration for WHITE."); - if (additional_knights_b + additional_bishops_b + additional_rooks_b + additional_queens_b > 8 - pawns_b) - UCI::critical_error("Invalid FEN. Invalid piece configuration for BLACK."); + if (rank != RANK_1 || file != FILE_NB) + UCI::critical_error("Invalid FEN. Board state encoding ended but cursor not at end."); + + { + const int pawns_w = count(WHITE); + const int pawns_b = count(BLACK); + if (pawns_w > 8) + UCI::critical_error("Invalid FEN. WHITE has more than 8 pawns."); + if (pawns_b > 8) + UCI::critical_error("Invalid FEN. BLACK has more than 8 pawns."); + + const int additional_knights_w = std::max((int)count(WHITE) - 2, 0); + const int additional_knights_b = std::max((int)count(BLACK) - 2, 0); + const int additional_bishops_w = std::max((int)count(WHITE) - 2, 0); + const int additional_bishops_b = std::max((int)count(BLACK) - 2, 0); + const int additional_rooks_w = std::max((int)count(WHITE) - 2, 0); + const int additional_rooks_b = std::max((int)count(BLACK) - 2, 0); + const int additional_queens_w = std::max((int)count(WHITE) - 1, 0); + const int additional_queens_b = std::max((int)count(BLACK) - 1, 0); + if (additional_knights_w + additional_bishops_w + additional_rooks_w + additional_queens_w > 8 - pawns_w) + UCI::critical_error("Invalid FEN. Invalid piece configuration for WHITE."); + if (additional_knights_b + additional_bishops_b + additional_rooks_b + additional_queens_b > 8 - pawns_b) + UCI::critical_error("Invalid FEN. Invalid piece configuration for BLACK."); + } + + ss >> std::ws; // 2. Active color - ss >> token; + if (!(ss >> token)) + UCI::critical_error("Invalid FEN. Unexpected end of stream."); if (token != 'w' && token != 'b') - UCI::critical_error(std::string("Invalid FEN. Invalid side to move: ") + std::string(1, token)); + UCI::critical_error(std::string("Invalid FEN. Invalid side to move: ") + std::string(1, token)); sideToMove = (token == 'w' ? WHITE : BLACK); - ss >> token; + + ss >> std::ws; // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, // Shredder-FEN that uses the letters of the columns on which the rooks began // the game instead of KQkq and also X-FEN standard that, in case of Chess960, // if an inner rook is associated with the castling right, the castling tag is // replaced by the file letter of the involved rook, as for the Shredder-FEN. - while ((ss >> token) && !isspace(token)) + int num_castling_rights = 0; + for (;;) { + if (!(ss >> token)) + UCI::critical_error("Invalid FEN. Unexpected end of stream."); + + if (isspace(token)) + break; + + if (num_castling_rights == 0 && token == '-') + break; + + if (++num_castling_rights > 4) + UCI::critical_error("Invalid FEN. Maximum of 4 castling rights can be specified."); + Square rsq; Color c = islower(token) ? BLACK : WHITE; Piece rook = make_piece(c, ROOK); @@ -268,57 +315,74 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th token = char(toupper(token)); if (token == 'K') - for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} - + for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook && file_of(rsq) >= FILE_A; --rsq) {} else if (token == 'Q') - for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} - + for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook && file_of(rsq) <= FILE_H; ++rsq) {} else if (token >= 'A' && token <= 'H') rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); - else if (token == '-') - break; - else UCI::critical_error(std::string("Invalid FEN. Expected castling rights. Got: ") + std::string(1, token)); + if (piece_on(rsq) != rook) + UCI::critical_error("Invalid FEN. Trying to set castling rights without required rook."); + set_castling_right(c, rsq); } - ss >> std::skipws; + ss >> std::ws; - // 4. En passant square. - // Ignore if square is invalid or not on side to move relative rank 6. + // 4. En passant square. Faux ep-square is ignored. Otherwise invalid ep-square is an error. bool enpassant = false; - - if (((ss >> col) && col != '-') && (ss >> row)) + unsigned char col, row; + if (!(ss >> col)) + UCI::critical_error("Invalid FEN. Unexpected end of stream."); + if (col != '-') { + if (!(ss >> row)) + UCI::critical_error("Invalid FEN. Unexpected end of stream."); + if ( (col >= 'a' && col <= 'h') && (row == (sideToMove == WHITE ? '6' : '3'))) { - st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - - // En passant square will be considered only if - // a) side to move have a pawn threatening epSquare - // b) there is an enemy pawn in front of epSquare - // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); } else - { - UCI::critical_error("Invalid FEN. Invalid en-passant square."); - } + UCI::critical_error("Invalid FEN. Invalid en-passant square."); } if (!enpassant) st->epSquare = SQ_NONE; - // 5-6. Halfmove clock and fullmove number - ss >> std::skipws >> st->rule50 >> gamePly; + ss >> std::skipws; - if (st->rule50 > 100) - UCI::critical_error("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); + // 5-6. Halfmove clock and fullmove number. Either none or both must be present. + // If they are not present then values are the same as for startpos. + if (ss >> st->rule50) + { + if (!(ss >> gamePly)) + UCI::critical_error("Invalid FEN. Unexpected end of stream or invalid numbers."); + } + else + { + st->rule50 = 0; + gamePly = 1; + } + + // Technically, positions with rule50==100 are correct, just no moves can be made further. + if (st->rule50 < 0 || st->rule50 > 100) + UCI::critical_error("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); + + // https://chess.stackexchange.com/questions/4113/longest-chess-game-possible-maximum-moves + if (gamePly < 1 || gamePly > 5900) + UCI::critical_error("Invalid FEN. Full-move counter outside of range, got: " + std::to_string(gamePly)); // Convert from fullmove starting from 1 to gamePly starting from 0, // handle also common incorrect FEN with fullmove = 0. @@ -326,10 +390,11 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th chess960 = isChess960; thisThread = th; - set_state(); if (!pos_is_ok()) - UCI::critical_error("Invalid FEN."); + UCI::critical_error("Invalid FEN."); + + set_state(); return *this; } diff --git a/src/uci.cpp b/src/uci.cpp index e0548fe8666..3e994adecbe 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -73,10 +73,13 @@ namespace { // Parse the move list, if any while (is >> token) { + if (pos.state()->rule50 > 99) + UCI::critical_error("Invalid move. Draw by rule50 reached."); + m = UCI::to_move(pos, token); if (m == MOVE_NONE) - UCI::critical_error("Invalid moves. Illegal move."); + UCI::critical_error("Invalid moves. Illegal move."); states->emplace_back(); pos.do_move(m, states->back()); From f39ca49c99a6b4dacc996e2d923160b7db6b83c7 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Fri, 12 May 2023 10:56:32 +0200 Subject: [PATCH 06/11] Allow rule50 up to 150. --- src/position.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/position.cpp b/src/position.cpp index a1bea9feee6..3e41eea90ff 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -377,7 +377,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th } // Technically, positions with rule50==100 are correct, just no moves can be made further. - if (st->rule50 < 0 || st->rule50 > 100) + // However, due to human stuff, we want to *support* rule50 up to 150. + if (st->rule50 < 0 || st->rule50 > 150) UCI::critical_error("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); // https://chess.stackexchange.com/questions/4113/longest-chess-game-possible-maximum-moves From e1a298e8c64323eb2ceb33c0d586541f1f953adc Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Fri, 12 May 2023 11:02:23 +0200 Subject: [PATCH 07/11] Adjust max number of moves calculation. --- src/position.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 3e41eea90ff..df0d8a06dce 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -378,11 +378,17 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // Technically, positions with rule50==100 are correct, just no moves can be made further. // However, due to human stuff, we want to *support* rule50 up to 150. - if (st->rule50 < 0 || st->rule50 > 150) + constexpr int max_rule50_fullmoves = 75; + if (st->rule50 < 0 || st->rule50 > max_rule50_fullmoves * 2) UCI::critical_error("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); // https://chess.stackexchange.com/questions/4113/longest-chess-game-possible-maximum-moves - if (gamePly < 1 || gamePly > 5900) + constexpr int max_moves = max_rule50_fullmoves - 1 + + 32 * max_rule50_fullmoves + + 6 * 8 * max_rule50_fullmoves + + 7 * max_rule50_fullmoves + + 30 * max_rule50_fullmoves; + if (gamePly < 1 || gamePly > max_moves) UCI::critical_error("Invalid FEN. Full-move counter outside of range, got: " + std::to_string(gamePly)); // Convert from fullmove starting from 1 to gamePly starting from 0, From d341695868f8b6b24a59a6a78595995e2129b7e3 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Mon, 15 May 2023 10:52:44 +0200 Subject: [PATCH 08/11] Move critical UCI error handling from position to the UCI layer. --- src/endgame.h | 9 +++++-- src/position.cpp | 58 +++++++++++++++++++++--------------------- src/position.h | 10 ++++++-- src/syzygy/tbprobe.cpp | 6 +++-- src/uci.cpp | 10 +++++--- src/uci.h | 2 +- 6 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/endgame.h b/src/endgame.h index c184cb3fd50..0ab1c40864b 100644 --- a/src/endgame.h +++ b/src/endgame.h @@ -109,9 +109,14 @@ namespace Endgames { template> void add(const std::string& code) { + Position pos; StateInfo st; - map()[Position().set(code, WHITE, &st).material_key()] = Ptr(new Endgame(WHITE)); - map()[Position().set(code, BLACK, &st).material_key()] = Ptr(new Endgame(BLACK)); + + pos.set(code, WHITE, &st); + map()[pos.material_key()] = Ptr(new Endgame(WHITE)); + + pos.set(code, BLACK, &st); + map()[pos.material_key()] = Ptr(new Endgame(BLACK)); } template diff --git a/src/position.cpp b/src/position.cpp index df0d8a06dce..a3314fb9529 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -157,7 +157,7 @@ void Position::init() { /// This function is not very robust - make sure that input FENs are correct, /// this is assumed to be the responsibility of the GUI. -Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { +std::optional Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* A FEN string defines a particular position using only the ASCII character set. @@ -210,7 +210,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th for (;;) { if (!(ss >> token)) - UCI::critical_error("Invalid FEN. Unexpected end of stream."); + return PositionSetError("Invalid FEN. Unexpected end of stream."); if (isspace(token)) break; @@ -219,49 +219,49 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th { const int diff = token - '0'; if (diff < 1 || diff > 8) - UCI::critical_error("Invalid FEN. Invalid number of squares to skip."); + return PositionSetError("Invalid FEN. Invalid number of squares to skip."); file = File(file + diff); if (file > FILE_NB) - UCI::critical_error("Invalid FEN. Invalid file reached."); + return PositionSetError("Invalid FEN. Invalid file reached."); } else if (token == '/') { if (file != FILE_NB) - UCI::critical_error("Invalid FEN. Trying to end rank when not at the end of it."); + return PositionSetError("Invalid FEN. Trying to end rank when not at the end of it."); --rank; file = FILE_A; if (rank < RANK_1) - UCI::critical_error("Invalid FEN. Invalid rank reached."); + return PositionSetError("Invalid FEN. Invalid rank reached."); } else { const size_t idx = PieceToChar.find(token); if (idx == string::npos) - UCI::critical_error(std::string("Invalid FEN. Invalid piece: ") + std::string(1, token)); + return PositionSetError(std::string("Invalid FEN. Invalid piece: ") + std::string(1, token)); if (++piece_count > 32) - UCI::critical_error("Invalid FEN. More than 32 pieces on the board."); + return PositionSetError("Invalid FEN. More than 32 pieces on the board."); const Square sq = make_square(file, rank); put_piece(Piece(idx), sq); ++file; if (file > FILE_NB) - UCI::critical_error("Invalid FEN. Invalid file reached."); + return PositionSetError("Invalid FEN. Invalid file reached."); } } if (rank != RANK_1 || file != FILE_NB) - UCI::critical_error("Invalid FEN. Board state encoding ended but cursor not at end."); + return PositionSetError("Invalid FEN. Board state encoding ended but cursor not at end."); { const int pawns_w = count(WHITE); const int pawns_b = count(BLACK); if (pawns_w > 8) - UCI::critical_error("Invalid FEN. WHITE has more than 8 pawns."); + return PositionSetError("Invalid FEN. WHITE has more than 8 pawns."); if (pawns_b > 8) - UCI::critical_error("Invalid FEN. BLACK has more than 8 pawns."); + return PositionSetError("Invalid FEN. BLACK has more than 8 pawns."); const int additional_knights_w = std::max((int)count(WHITE) - 2, 0); const int additional_knights_b = std::max((int)count(BLACK) - 2, 0); @@ -272,18 +272,18 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th const int additional_queens_w = std::max((int)count(WHITE) - 1, 0); const int additional_queens_b = std::max((int)count(BLACK) - 1, 0); if (additional_knights_w + additional_bishops_w + additional_rooks_w + additional_queens_w > 8 - pawns_w) - UCI::critical_error("Invalid FEN. Invalid piece configuration for WHITE."); + return PositionSetError("Invalid FEN. Invalid piece configuration for WHITE."); if (additional_knights_b + additional_bishops_b + additional_rooks_b + additional_queens_b > 8 - pawns_b) - UCI::critical_error("Invalid FEN. Invalid piece configuration for BLACK."); + return PositionSetError("Invalid FEN. Invalid piece configuration for BLACK."); } ss >> std::ws; // 2. Active color if (!(ss >> token)) - UCI::critical_error("Invalid FEN. Unexpected end of stream."); + return PositionSetError("Invalid FEN. Unexpected end of stream."); if (token != 'w' && token != 'b') - UCI::critical_error(std::string("Invalid FEN. Invalid side to move: ") + std::string(1, token)); + return PositionSetError(std::string("Invalid FEN. Invalid side to move: ") + std::string(1, token)); sideToMove = (token == 'w' ? WHITE : BLACK); ss >> std::ws; @@ -297,7 +297,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th for (;;) { if (!(ss >> token)) - UCI::critical_error("Invalid FEN. Unexpected end of stream."); + return PositionSetError("Invalid FEN. Unexpected end of stream."); if (isspace(token)) break; @@ -306,7 +306,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th break; if (++num_castling_rights > 4) - UCI::critical_error("Invalid FEN. Maximum of 4 castling rights can be specified."); + return PositionSetError("Invalid FEN. Maximum of 4 castling rights can be specified."); Square rsq; Color c = islower(token) ? BLACK : WHITE; @@ -321,10 +321,10 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th else if (token >= 'A' && token <= 'H') rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); else - UCI::critical_error(std::string("Invalid FEN. Expected castling rights. Got: ") + std::string(1, token)); + return PositionSetError(std::string("Invalid FEN. Expected castling rights. Got: ") + std::string(1, token)); if (piece_on(rsq) != rook) - UCI::critical_error("Invalid FEN. Trying to set castling rights without required rook."); + return PositionSetError("Invalid FEN. Trying to set castling rights without required rook."); set_castling_right(c, rsq); } @@ -335,11 +335,11 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th bool enpassant = false; unsigned char col, row; if (!(ss >> col)) - UCI::critical_error("Invalid FEN. Unexpected end of stream."); + return PositionSetError("Invalid FEN. Unexpected end of stream."); if (col != '-') { if (!(ss >> row)) - UCI::critical_error("Invalid FEN. Unexpected end of stream."); + return PositionSetError("Invalid FEN. Unexpected end of stream."); if ( (col >= 'a' && col <= 'h') && (row == (sideToMove == WHITE ? '6' : '3'))) @@ -355,7 +355,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); } else - UCI::critical_error("Invalid FEN. Invalid en-passant square."); + return PositionSetError("Invalid FEN. Invalid en-passant square."); } if (!enpassant) @@ -368,7 +368,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th if (ss >> st->rule50) { if (!(ss >> gamePly)) - UCI::critical_error("Invalid FEN. Unexpected end of stream or invalid numbers."); + return PositionSetError("Invalid FEN. Unexpected end of stream or invalid numbers."); } else { @@ -380,7 +380,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // However, due to human stuff, we want to *support* rule50 up to 150. constexpr int max_rule50_fullmoves = 75; if (st->rule50 < 0 || st->rule50 > max_rule50_fullmoves * 2) - UCI::critical_error("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); + return PositionSetError("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); // https://chess.stackexchange.com/questions/4113/longest-chess-game-possible-maximum-moves constexpr int max_moves = max_rule50_fullmoves - 1 @@ -389,7 +389,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th + 7 * max_rule50_fullmoves + 30 * max_rule50_fullmoves; if (gamePly < 1 || gamePly > max_moves) - UCI::critical_error("Invalid FEN. Full-move counter outside of range, got: " + std::to_string(gamePly)); + return PositionSetError("Invalid FEN. Full-move counter outside of range, got: " + std::to_string(gamePly)); // Convert from fullmove starting from 1 to gamePly starting from 0, // handle also common incorrect FEN with fullmove = 0. @@ -399,11 +399,11 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th thisThread = th; if (!pos_is_ok()) - UCI::critical_error("Invalid FEN."); + return PositionSetError("Invalid FEN."); set_state(); - return *this; + return std::nullopt; } @@ -490,7 +490,7 @@ void Position::set_state() const { /// the given endgame code string like "KBPKN". It is mainly a helper to /// get the material key out of an endgame code. -Position& Position::set(const string& code, Color c, StateInfo* si) { +std::optional Position::set(const string& code, Color c, StateInfo* si) { assert(code[0] == 'K'); diff --git a/src/position.h b/src/position.h index a736f3e677b..18b4925eeab 100644 --- a/src/position.h +++ b/src/position.h @@ -23,6 +23,8 @@ #include #include // For std::unique_ptr #include +#include +#include #include "bitboard.h" #include "evaluate.h" @@ -37,6 +39,10 @@ namespace Stockfish { /// its previous state when we retract a move. Whenever a move is made on the /// board (by calling Position::do_move), a StateInfo object must be passed. +struct PositionSetError : std::runtime_error { + using std::runtime_error::runtime_error; +}; + struct StateInfo { // Copied when making a move @@ -86,8 +92,8 @@ class Position { Position& operator=(const Position&) = delete; // FEN string input/output - Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); - Position& set(const std::string& code, Color c, StateInfo* si); + std::optional set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + std::optional set(const std::string& code, Color c, StateInfo* si); std::string fen() const; // Position representation diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 9cb0bfdbc0f..d1096ab26e0 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -363,7 +363,8 @@ TBTable::TBTable(const std::string& code) : TBTable() { StateInfo st; Position pos; - key = pos.set(code, WHITE, &st).material_key(); + pos.set(code, WHITE, &st); + key = pos.material_key(); pieceCount = pos.count(); hasPawns = pos.pieces(PAWN); @@ -382,7 +383,8 @@ TBTable::TBTable(const std::string& code) : TBTable() { pawnCount[0] = pos.count(c ? WHITE : BLACK); pawnCount[1] = pos.count(c ? BLACK : WHITE); - key2 = pos.set(code, BLACK, &st).material_key(); + pos.set(code, BLACK, &st); + key2 = pos.material_key(); } template<> diff --git a/src/uci.cpp b/src/uci.cpp index 3e994adecbe..1c6286508ab 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -68,18 +68,20 @@ namespace { return; states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one - pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + auto err = pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + if (err) + UCI::terminate_on_critical_error(err->what()); // Parse the move list, if any while (is >> token) { if (pos.state()->rule50 > 99) - UCI::critical_error("Invalid move. Draw by rule50 reached."); + UCI::terminate_on_critical_error("Invalid move. Draw by rule50 reached."); m = UCI::to_move(pos, token); if (m == MOVE_NONE) - UCI::critical_error("Invalid moves. Illegal move."); + UCI::terminate_on_critical_error("Invalid moves. Illegal move."); states->emplace_back(); pos.do_move(m, states->back()); @@ -403,7 +405,7 @@ Move UCI::to_move(const Position& pos, string& str) { return MOVE_NONE; } -void UCI::critical_error(const std::string& message) { +void UCI::terminate_on_critical_error(const std::string& message) { // Ideally we would report the error and continue, but UCI does not define such a situation nor // gives any guarantees as to the program's behaviour for the user to rely on, // so the safest option is to terminate. diff --git a/src/uci.h b/src/uci.h index 422e1c822b3..49a56729689 100644 --- a/src/uci.h +++ b/src/uci.h @@ -83,7 +83,7 @@ std::string pv(const Position& pos, Depth depth); std::string wdl(Value v, int ply); Move to_move(const Position& pos, std::string& str); -[[noreturn]] void critical_error(const std::string& message); +[[noreturn]] void terminate_on_critical_error(const std::string& message); } // namespace UCI From c79202c35bc8a4d5e3829c0d4be99dfc48045815 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Mon, 15 May 2023 11:12:41 +0200 Subject: [PATCH 09/11] Update comments on position fen parsing. --- src/position.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index a3314fb9529..f29b7f8d727 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -154,8 +154,21 @@ void Position::init() { /// Position::set() initializes the position object with the given FEN string. -/// This function is not very robust - make sure that input FENs are correct, -/// this is assumed to be the responsibility of the GUI. +/// Some validation is performed and positions that are invalid, or not supported, +/// or potentially not supported, by Stockfish, will be rejected and an error returned. +/// The state of the Position after an error is undefined. In such case Position::set must be +/// called again with a valid position before the Position object can be used. +/// +/// Stockfish informally requires that the position passed to Position::set is reachable from +/// chess (or any chess960) starting position. +/// Validation, however, will only reject the following kinds of positions (provided the FEN itself is valid in the first place): +/// - any side has less or more than 1 king +/// - opponent's king is in check +/// - any side has more than 16 pieces +/// - any side has more than 8 pawns +/// - any side has material configuration that is unattainable through pawn promotions +/// - rule50 counter is above 150 (75-move rule, though note that any non-mate position with rule50>100 is considered a draw) +/// - fullmove counter exceeds the maximum theoretical game length std::optional Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* @@ -187,10 +200,10 @@ std::optional Position::set(const string& fenStr, bool isChess 5) Halfmove clock. This is the number of halfmoves since the last pawn advance or capture. This is used to determine if a draw can be claimed under the - fifty-move rule. + fifty-move rule. This field is optional. Default: 0. 6) Fullmove number. The number of the full move. It starts at 1, and is - incremented after Black's move. + incremented after Black's move. This field is optional. Default: 1. */ unsigned char token; From 0c19465ab191146f66c8c23a83c0203826c85154 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Tue, 16 May 2023 11:44:10 +0200 Subject: [PATCH 10/11] Fix naming convention. --- src/position.cpp | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index f29b7f8d727..51e55aaaef5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -215,7 +215,7 @@ std::optional Position::set(const string& fenStr, bool isChess ss >> std::noskipws; - int piece_count = 0; + int numPieces = 0; File file = FILE_A; Rank rank = RANK_8; @@ -253,7 +253,7 @@ std::optional Position::set(const string& fenStr, bool isChess const size_t idx = PieceToChar.find(token); if (idx == string::npos) return PositionSetError(std::string("Invalid FEN. Invalid piece: ") + std::string(1, token)); - if (++piece_count > 32) + if (++numPieces > 32) return PositionSetError("Invalid FEN. More than 32 pieces on the board."); const Square sq = make_square(file, rank); @@ -269,24 +269,24 @@ std::optional Position::set(const string& fenStr, bool isChess return PositionSetError("Invalid FEN. Board state encoding ended but cursor not at end."); { - const int pawns_w = count(WHITE); - const int pawns_b = count(BLACK); - if (pawns_w > 8) + const int wPawns = count(WHITE); + const int bPawns = count(BLACK); + if (wPawns > 8) return PositionSetError("Invalid FEN. WHITE has more than 8 pawns."); - if (pawns_b > 8) + if (bPawns > 8) return PositionSetError("Invalid FEN. BLACK has more than 8 pawns."); - const int additional_knights_w = std::max((int)count(WHITE) - 2, 0); - const int additional_knights_b = std::max((int)count(BLACK) - 2, 0); - const int additional_bishops_w = std::max((int)count(WHITE) - 2, 0); - const int additional_bishops_b = std::max((int)count(BLACK) - 2, 0); - const int additional_rooks_w = std::max((int)count(WHITE) - 2, 0); - const int additional_rooks_b = std::max((int)count(BLACK) - 2, 0); - const int additional_queens_w = std::max((int)count(WHITE) - 1, 0); - const int additional_queens_b = std::max((int)count(BLACK) - 1, 0); - if (additional_knights_w + additional_bishops_w + additional_rooks_w + additional_queens_w > 8 - pawns_w) + const int wAdditionalKnights = std::max((int)count(WHITE) - 2, 0); + const int bAdditionalKnights = std::max((int)count(BLACK) - 2, 0); + const int wAdditionalBishops = std::max((int)count(WHITE) - 2, 0); + const int bAdditionalBishops = std::max((int)count(BLACK) - 2, 0); + const int wAdditionalRooks = std::max((int)count(WHITE) - 2, 0); + const int bAdditionalRooks = std::max((int)count(BLACK) - 2, 0); + const int wAdditionalQueens = std::max((int)count(WHITE) - 1, 0); + const int bAdditionalQueens = std::max((int)count(BLACK) - 1, 0); + if (wAdditionalKnights + wAdditionalBishops + wAdditionalRooks + wAdditionalQueens > 8 - wPawns) return PositionSetError("Invalid FEN. Invalid piece configuration for WHITE."); - if (additional_knights_b + additional_bishops_b + additional_rooks_b + additional_queens_b > 8 - pawns_b) + if (bAdditionalKnights + bAdditionalBishops + bAdditionalRooks + bAdditionalQueens > 8 - bPawns) return PositionSetError("Invalid FEN. Invalid piece configuration for BLACK."); } @@ -391,17 +391,17 @@ std::optional Position::set(const string& fenStr, bool isChess // Technically, positions with rule50==100 are correct, just no moves can be made further. // However, due to human stuff, we want to *support* rule50 up to 150. - constexpr int max_rule50_fullmoves = 75; - if (st->rule50 < 0 || st->rule50 > max_rule50_fullmoves * 2) + constexpr int MaxRule50Fullmoves = 75; + if (st->rule50 < 0 || st->rule50 > MaxRule50Fullmoves * 2) return PositionSetError("Invalid FEN. Rule50 counter outside of range, got: " + std::to_string(st->rule50)); // https://chess.stackexchange.com/questions/4113/longest-chess-game-possible-maximum-moves - constexpr int max_moves = max_rule50_fullmoves - 1 - + 32 * max_rule50_fullmoves - + 6 * 8 * max_rule50_fullmoves - + 7 * max_rule50_fullmoves - + 30 * max_rule50_fullmoves; - if (gamePly < 1 || gamePly > max_moves) + constexpr int MaxMoves = MaxRule50Fullmoves - 1 + + 32 * MaxRule50Fullmoves + + 6 * 8 * MaxRule50Fullmoves + + 7 * MaxRule50Fullmoves + + 30 * MaxRule50Fullmoves; + if (gamePly < 1 || gamePly > MaxMoves) return PositionSetError("Invalid FEN. Full-move counter outside of range, got: " + std::to_string(gamePly)); // Convert from fullmove starting from 1 to gamePly starting from 0, From fb1a7f872001fa2cb0e357615b8b7b46d3e9e9ff Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Wed, 17 May 2023 15:34:08 +0200 Subject: [PATCH 11/11] In the critical error message print the full command that failed. --- src/uci.cpp | 12 +++++++----- src/uci.h | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 1c6286508ab..25c6e8ea07c 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -54,6 +54,8 @@ namespace { Move m; string token, fen; + const string fullCommand = is.str(); + is >> token; if (token == "startpos") @@ -70,18 +72,18 @@ namespace { states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one auto err = pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); if (err) - UCI::terminate_on_critical_error(err->what()); + UCI::terminate_on_critical_error(fullCommand, err->what()); // Parse the move list, if any while (is >> token) { if (pos.state()->rule50 > 99) - UCI::terminate_on_critical_error("Invalid move. Draw by rule50 reached."); + UCI::terminate_on_critical_error(fullCommand, "Invalid move. Draw by rule50 reached."); m = UCI::to_move(pos, token); if (m == MOVE_NONE) - UCI::terminate_on_critical_error("Invalid moves. Illegal move."); + UCI::terminate_on_critical_error(fullCommand, "Invalid moves. Illegal move."); states->emplace_back(); pos.do_move(m, states->back()); @@ -405,11 +407,11 @@ Move UCI::to_move(const Position& pos, string& str) { return MOVE_NONE; } -void UCI::terminate_on_critical_error(const std::string& message) { +void UCI::terminate_on_critical_error(const std::string& fullCommand, const std::string& message) { // Ideally we would report the error and continue, but UCI does not define such a situation nor // gives any guarantees as to the program's behaviour for the user to rely on, // so the safest option is to terminate. - sync_cout << "info string CRITICAL ERROR: " << message << '\n' << sync_endl; + sync_cout << "info string CRITICAL ERROR: Command `" << fullCommand << "` failed. Reason: " << message << '\n' << sync_endl; std::terminate(); } diff --git a/src/uci.h b/src/uci.h index 49a56729689..2d1f433099b 100644 --- a/src/uci.h +++ b/src/uci.h @@ -83,7 +83,7 @@ std::string pv(const Position& pos, Depth depth); std::string wdl(Value v, int ply); Move to_move(const Position& pos, std::string& str); -[[noreturn]] void terminate_on_critical_error(const std::string& message); +[[noreturn]] void terminate_on_critical_error(const std::string& fullCommand, const std::string& message); } // namespace UCI