From a66c0afe4cfb63e59bfe5ffe61e4bd4e4367ce72 Mon Sep 17 00:00:00 2001 From: Holger Schemel Date: Thu, 4 Jan 2007 22:53:21 +0100 Subject: [PATCH] rnd-20070104-2-src * added workaround (warning and request to quit the current game) when changing elements cause endless recursion loop (which would otherwise freeze the game, causing a crash-like program exit on some systems) --- ChangeLog | 3 ++ src/conftime.h | 2 +- src/game.c | 94 +++++++++++++++++++++++++++++++++++++++++++++----- src/game.h | 2 ++ 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6825dbd4..d639ab01 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2007-01-04 * fixed malloc/free bug when updating EMC artwork entries in level list + * added workaround (warning and request to quit the current game) when + changing elements cause endless recursion loop (which would otherwise + freeze the game, causing a crash-like program exit on some systems) 2006-12-16 * fixed nasty string overflow bug when entering too long envelope text diff --git a/src/conftime.h b/src/conftime.h index 32bbcff9..0d9cc6ac 100644 --- a/src/conftime.h +++ b/src/conftime.h @@ -1 +1 @@ -#define COMPILE_DATE_STRING "[2007-01-04 17:28]" +#define COMPILE_DATE_STRING "[2007-01-04 20:10]" diff --git a/src/game.c b/src/game.c index 3f3e6317..00a1a090 100644 --- a/src/game.c +++ b/src/game.c @@ -379,6 +379,33 @@ static int getInvisibleFromInvisibleActiveElement(int); static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS]; +/* for detection of endless loops, caused by custom element programming */ +/* (using "MAX_PLAYFIELD_WIDTH" here is just a rough approximation...) */ +#define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH) + +#define RECURSION_LOOP_DETECTION_START(e, rc) \ +{ \ + if (recursion_loop_detected) \ + return (rc); \ + \ + if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \ + { \ + recursion_loop_detected = TRUE; \ + recursion_loop_element = (e); \ + } \ + \ + recursion_loop_depth++; \ +} + +#define RECURSION_LOOP_DETECTION_END() \ +{ \ + recursion_loop_depth--; \ +} + +static int recursion_loop_depth; +static boolean recursion_loop_detected; +static boolean recursion_loop_element; + /* ------------------------------------------------------------------------- */ /* definition of elements that automatically change to other elements after */ @@ -1786,6 +1813,11 @@ static void InitGameEngine() EL_EMPTY); } } + + /* ---------- initialize recursion detection ------------------------------ */ + recursion_loop_depth = 0; + recursion_loop_detected = FALSE; + recursion_loop_element = EL_UNDEFINED; } int get_num_special_action(int element, int action_first, int action_last) @@ -8578,6 +8610,14 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, if (!(trigger_events[trigger_element][trigger_event])) return FALSE; +#if 0 + printf("::: CheckTriggeredElementChangeExt %d ... [%d, %d, %d, '%s']\n", + trigger_event, recursion_loop_depth, recursion_loop_detected, + recursion_loop_element, EL_NAME(recursion_loop_element)); +#endif + + RECURSION_LOOP_DETECTION_START(trigger_element, FALSE); + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) { int element = EL_CUSTOM_START + i; @@ -8646,6 +8686,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, } } + RECURSION_LOOP_DETECTION_END(); + return change_done_any; } @@ -8684,6 +8726,14 @@ static boolean CheckElementChangeExt(int x, int y, return FALSE; #endif +#if 0 + printf("::: CheckElementChangeExt %d ... [%d, %d, %d, '%s']\n", + trigger_event, recursion_loop_depth, recursion_loop_detected, + recursion_loop_element, EL_NAME(recursion_loop_element)); +#endif + + RECURSION_LOOP_DETECTION_START(trigger_element, FALSE); + for (p = 0; p < element_info[element].num_change_pages; p++) { struct ElementChangeInfo *change = &element_info[element].change_page[p]; @@ -8763,6 +8813,8 @@ static boolean CheckElementChangeExt(int x, int y, } } + RECURSION_LOOP_DETECTION_END(); + return change_done; } @@ -9164,6 +9216,25 @@ void GameActions() byte tape_action[MAX_PLAYERS]; int i; + /* detect endless loops, caused by custom element programming */ + if (recursion_loop_detected && recursion_loop_depth == 0) + { + char *message = getStringCat3("Internal Error ! Element ", + EL_NAME(recursion_loop_element), + " caused endless loop ! Quit the game ?"); + + Error(ERR_WARN, "element '%s' caused endless loop in game engine", + EL_NAME(recursion_loop_element)); + + RequestQuitGameExt(FALSE, level_editor_test_game, message); + + recursion_loop_detected = FALSE; /* if game should be continued */ + + free(message); + + return; + } + if (game.restart_level) StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE); @@ -11096,8 +11167,10 @@ void KillPlayer(struct PlayerInfo *player) "kill player X when explosion of "; the solution using a new field "player->killed" was chosen for backwards compatibility, although clever use of the fields "player->active" etc. would probably also work */ +#if 1 if (player->killed) return; +#endif player->killed = TRUE; @@ -12536,13 +12609,9 @@ void RaiseScoreElement(int element) } } -void RequestQuitGame(boolean ask_if_really_quit) +void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) { - if (AllPlayersGone || - !ask_if_really_quit || - level_editor_test_game || - Request("Do you really want to quit the game ?", - REQ_ASK | REQ_STAY_CLOSED)) + if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED)) { #if defined(NETWORK_AVALIABLE) if (options.network) @@ -12550,7 +12619,7 @@ void RequestQuitGame(boolean ask_if_really_quit) else #endif { - if (!ask_if_really_quit || level_editor_test_game) + if (quick_quit) { game_status = GAME_MODE_MAIN; @@ -12566,7 +12635,7 @@ void RequestQuitGame(boolean ask_if_really_quit) } } } - else + else /* continue playing the game */ { if (tape.playing && tape.deactivate_display) TapeDeactivateDisplayOff(TRUE); @@ -12578,6 +12647,15 @@ void RequestQuitGame(boolean ask_if_really_quit) } } +void RequestQuitGame(boolean ask_if_really_quit) +{ + boolean quick_quit = (!ask_if_really_quit || level_editor_test_game); + boolean skip_request = AllPlayersGone || quick_quit; + + RequestQuitGameExt(skip_request, quick_quit, + "Do you really want to quit the game ?"); +} + /* ------------------------------------------------------------------------- */ /* random generator functions */ diff --git a/src/game.h b/src/game.h index c7e70c31..cf5d2c28 100644 --- a/src/game.h +++ b/src/game.h @@ -241,6 +241,8 @@ void PlayLevelSound_EM(int, int, int, int); void RaiseScore(int); void RaiseScoreElement(int); + +void RequestQuitGameExt(boolean, boolean, char *); void RequestQuitGame(boolean); unsigned int InitEngineRandom_RND(long); -- 2.34.1