fix #K3917 - fuzzer stuck in endless lifesave loop

Life-saving from being burned up in lava attempts to teleport the
hero to safely.  If that fails, hero immediately burns up again.
For fuzz testing, that results in an infinite loop.

While implementing a fix (in done(), not just lava-specific), I
noticed that hangup while running interactively in explore or
wizard mode could be subject to similar effect.

For the fuzzer, if hero dies 15 times without advancing the move
count (not 'moves', the turn count), don't life-save again.  With
hangup, don't prompt for "Die?" more than once.

Normal interactive declining to die still works.  The more exotic
situations aren't tested.
This commit is contained in:
PatR
2023-05-06 16:51:59 -07:00
parent 5023e4bfad
commit ca02bc4898
4 changed files with 39 additions and 2 deletions

View File

@@ -1172,6 +1172,12 @@ engraving in a breach in a shop's or vault's wall or vault guard's temporary
if a fire resistant non-fire immune monster wearing a thoroughly burnt wooden
shield got knocked into lava, burning the shield completely yielded
impossible warning "obfree: deleting worn obj"
hangup in wizard or explore mode would result in answering ESC to "Die?"
prompt if that was reached, and since default is 'no' the hero would
be life-saved and the game would try to keep going; if circumstances
resulted in repeat death then the program might get stuck in a loop
instead of exiting [no reports of such, but if it ever happened the
process was probably killed without anyone knowing why it happened]
Fixes to 3.7.0-x General Problems Exposed Via git Repository
@@ -1564,6 +1570,10 @@ buried troll whose auto-revive timer expired might triger panic with
"revive default case 6"
throwing a helm of brilliance could yield "breaking odd object?"
sanity checking of engravings was stopping after first problem found
the fuzzer could get stuck in a loop if hero died in a way where life-saving
just resulted in a repeat death (cited case was burning up in lava,
where life-saving teleports you out of it; if the level is full, the
teleport will fail and you'll immediately burn up again)
Fixes to 3.7.0-x Platform and/or Interface Problems Exposed Via git Repository

View File

@@ -307,6 +307,9 @@ struct instance_globals_d {
/* dog.c */
char dogname[PL_PSIZ];
/* end.c */
long done_seq; /* for counting deaths occurring on same hero_seq */
/* mon.c */
boolean disintegested;

View File

@@ -320,6 +320,8 @@ const struct instance_globals_d g_init_d = {
0, /* did_nothing_flag */
/* dog.c */
DUMMY, /* dogname */
/* end.c */
0L, /* done_seq */
/* mon.c */
FALSE, /* disintegested */
/* o_init.c */

View File

@@ -1173,8 +1173,23 @@ done(int how)
bot();
}
/* hero_seq is (moves<<3 + n) where n is number of moves made
by the hero on the current turn (since the 'moves' variable
actually counts turns); its details shouldn't matter here */
if (gd.done_seq < gh.hero_seq)
gd.done_seq = gh.hero_seq;
if (iflags.debug_fuzzer) {
if (!(gp.program_state.panicking || how == PANICKED)) {
if (!gp.program_state.panicking && how != PANICKED
/* Guard against getting stuck in a loop if we die in one of
* the few ways where life-saving isn't effective (cited case
* was burning in lava when the level was too full to allow
* teleporting to safety). Skip the life-save attempt if we've
* died on the same move more than 15 times; give up instead.
* (Note: theoretically we could get killed more than that in
* one move if there are multiple fast monsters with multiple
* attacks against a wimply hero, or a ton of ranged attacks.) */
&& (gd.done_seq++ < gh.hero_seq + 15L)) {
savelife(how);
/* periodically restore characteristics and lost exp levels
or cure lycanthropy */
@@ -1192,7 +1207,8 @@ done(int how)
gk.killer.format = 0;
return;
}
} else
}
if (how == ASCENDED || (!gk.killer.name[0] && how == GENOCIDED))
gk.killer.format = NO_KILLER_PREFIX;
/* Avoid killed by "a" burning or "a" starvation */
@@ -1237,6 +1253,12 @@ done(int how)
}
/* explore and wizard modes offer player the option to keep playing */
if (!survive && (wizard || discover) && how <= GENOCIDED
#ifdef HANGUPHANDLING
/* if hangup has occurred, the only possible answer to a paranoid
query is 'no'; we want 'no' as the default for "Die?" but can't
accept it more than once if there's no user supplying it */
&& !(gp.program_state.done_hup && gd.done_seq++ == gh.hero_seq)
#endif
&& !paranoid_query(ParanoidDie, "Die?")) {
pline("OK, so you don't %s.", (how == CHOKING) ? "choke" : "die");
iflags.last_msg = PLNMSG_OK_DONT_DIE;