From c6812bd3c527e50bf2ba8e4cb5355c7a6c67d0e4 Mon Sep 17 00:00:00 2001 From: Simon Fossom Date: Mon, 28 Sep 2026 01:05:56 +0200 Subject: [PATCH] execlist: save donetrap in a local, not the exiting parameter execlist() borrowed its 'exiting' parameter to preserve the static donetrap across dotrap(SIGDEBUG), and never restored it. After an ERR trap had set donetrap, the DEBUG trap for the last sublist of a list left 'exiting' set, so the check at the end of execlist() ran the EXIT trap (and cleared it) when that list finished, not when the shell exited. Save donetrap in a local instead, and add tests for both DEBUG_BEFORE_CMD settings. --- Src/exec.c | 13 +++++++++---- Test/C03traps.ztst | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Src/exec.c b/Src/exec.c index b0794abab..411090ede 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -1480,6 +1480,12 @@ execlist(Estate state, int dont_change_job, int exiting) if (sigtrapped[SIGDEBUG] && isset(DEBUGBEFORECMD) && !intrap) { Wordcode pc2 = state->pc; int oerrexit_opt = opts[ERREXIT]; + /* + * The trap recurses into execlist(), which resets the static + * donetrap: save it here, not in `exiting', which must keep + * meaning "the shell exits after this list". + */ + int odonetrap = donetrap; Param pm; opts[ERREXIT] = 0; noerrexit |= NOERREXIT_EXIT | NOERREXIT_RETURN; @@ -1489,12 +1495,11 @@ execlist(Estate state, int dont_change_job, int exiting) getpermtext(state->prog, pc2, 0), 0); - exiting = donetrap; ret = lastval; dotrap(SIGDEBUG); if (!retflag) lastval = ret; - donetrap = exiting; + donetrap = odonetrap; noerrexit = oldnoerrexit; /* * Only execute the trap once per sublist, even @@ -1635,14 +1640,14 @@ sublist_done: * DEBUGBEFORECMD, even though it's not used. */ int oerrexit_opt = opts[ERREXIT]; + int odonetrap = donetrap; /* not `exiting': see DEBUGBEFORECMD */ opts[ERREXIT] = 0; noerrexit |= NOERREXIT_EXIT | NOERREXIT_RETURN; - exiting = donetrap; ret = lastval; dotrap(SIGDEBUG); if (!retflag) lastval = ret; - donetrap = exiting; + donetrap = odonetrap; noerrexit = oldnoerrexit; opts[ERREXIT] = oerrexit_opt; } diff --git a/Test/C03traps.ztst b/Test/C03traps.ztst index 9924e4f96..1165e85a5 100644 --- a/Test/C03traps.ztst +++ b/Test/C03traps.ztst @@ -1196,6 +1196,26 @@ F:Must be tested with a top-level script rather than source or function >This is TRAPEXIT # Three explicit calls, one implicit call at function exit. + (cd ..; $ZTST_exe -fc 'TRAPDEBUG() { : }; TRAPERR() { : } + trap "print Exited" EXIT + f() { return 1 } + { f; print A } + print B') +0:DEBUG and ERR traps don't run the EXIT trap before the shell exits +>A +>B +>Exited + + (cd ..; $ZTST_exe -fc 'setopt nodebugbeforecmd + TRAPDEBUG() { : }; TRAPERR() { : } + trap "print Exited" EXIT + f() { return 1 } + { f } + print A') +0:Same with NO_DEBUG_BEFORE_CMD +>A +>Exited + %clean rm -f TRAPEXIT -- 2.55.0