From 3add0123c1265288b3aa8f41c5a0588735aaf9a3 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Wed, 9 Feb 2005 11:49:40 +0000 Subject: 20793 (part): Fix bug autoloading trap, improve trap tests --- ChangeLog | 5 + Src/signals.c | 336 +++++++++++++++++++++++++++++++++++++++-------------- Test/C03traps.ztst | 251 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 505 insertions(+), 87 deletions(-) create mode 100644 Test/C03traps.ztst diff --git a/ChangeLog b/ChangeLog index 0dc281bcf..7b003691a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2005-02-09 Peter Stephenson + + * 20793 (part): Src/signals.c, Test/C03traps.ztst: Port main + fix for autoloaded traps and tests. + 2005-02-04 Peter Stephenson * Motoi Washida: 20785: configure.ac: libiconv test needed diff --git a/Src/signals.c b/Src/signals.c index 7edf6f56e..e5225b6c5 100644 --- a/Src/signals.c +++ b/Src/signals.c @@ -41,14 +41,26 @@ mod_export int sigtrapped[VSIGCOUNT]; /**/ mod_export Eprog sigfuncs[VSIGCOUNT]; +/* Total count of trapped signals */ + +/**/ +mod_export int nsigtrapped; + /* Variables used by signal queueing */ /**/ -int queueing_enabled, queue_front, queue_rear; +mod_export int queueing_enabled, queue_front, queue_rear; /**/ -int signal_queue[MAX_QUEUE_SIZE]; +mod_export int signal_queue[MAX_QUEUE_SIZE]; /**/ -sigset_t signal_mask_queue[MAX_QUEUE_SIZE]; +mod_export sigset_t signal_mask_queue[MAX_QUEUE_SIZE]; + +/* Variables used by trap queueing */ + +/**/ +mod_export int trap_queueing_enabled, trap_queue_front, trap_queue_rear; +/**/ +mod_export int trap_queue[MAX_QUEUE_SIZE]; /* This is only used on machines that don't understand signal sets. * * On SYSV machines this will represent the signals that are blocked * @@ -88,7 +100,7 @@ install_handler(int sig) #ifdef POSIX_SIGNALS struct sigaction act; - act.sa_handler = (SIGNAL_HANDTYPE) handler; + act.sa_handler = (SIGNAL_HANDTYPE) zhandler; sigemptyset(&act.sa_mask); /* only block sig while in handler */ act.sa_flags = 0; # ifdef SA_INTERRUPT /* SunOS 4.x */ @@ -100,7 +112,7 @@ install_handler(int sig) # ifdef BSD_SIGNALS struct sigvec vec; - vec.sv_handler = (SIGNAL_HANDTYPE) handler; + vec.sv_handler = (SIGNAL_HANDTYPE) zhandler; vec.sv_mask = sigmask(sig); /* mask out this signal while in handler */ # ifdef SV_INTERRUPT vec.sv_flags = SV_INTERRUPT; /* make sure system calls are not restarted */ @@ -110,9 +122,9 @@ install_handler(int sig) # ifdef SYSV_SIGNALS /* we want sigset rather than signal because it will * * block sig while in handler. signal usually doesn't */ - sigset(sig, handler); + sigset(sig, zhandler); # else /* NO_SIGNAL_BLOCKING (bummer) */ - signal(sig, handler); + signal(sig, zhandler); # endif /* SYSV_SIGNALS */ # endif /* BSD_SIGNALS */ @@ -277,7 +289,7 @@ signal_unblock(sigset_t set) * be the given signal mask */ /**/ -sigset_t +mod_export sigset_t signal_setmask(sigset_t set) { sigset_t oset; @@ -338,11 +350,15 @@ signal_suspend(int sig, int sig2) sigset_t oset; #endif /* BROKEN_POSIX_SIGSUSPEND */ - sigfillset(&set); - sigdelset(&set, sig); - sigdelset(&set, SIGHUP); /* still don't know why we add this? */ - if (sig2) - sigdelset(&set, sig2); + if (isset(TRAPSASYNC)) { + sigemptyset(&set); + } else { + sigfillset(&set); + sigdelset(&set, sig); + sigdelset(&set, SIGHUP); /* still don't know why we add this? */ + if (sig2) + sigdelset(&set, sig2); + } #ifdef BROKEN_POSIX_SIGSUSPEND sigprocmask(SIG_SETMASK, &set, &oset); pause(); @@ -354,11 +370,15 @@ signal_suspend(int sig, int sig2) # ifdef BSD_SIGNALS sigset_t set; - sigfillset(&set); - sigdelset(&set, sig); - if (sig2) - sigdelset(&set, sig2); - ret = sigpause(set); + if (isset(TRAPSASYNC)) { + sigemptyset(&set); + } else { + sigfillset(&set); + sigdelset(&set, sig); + if (sig2) + sigdelset(&set, sig2); + ret = sigpause(set); + } # else # ifdef SYSV_SIGNALS ret = sigpause(sig); @@ -380,23 +400,11 @@ signal_suspend(int sig, int sig2) return ret; } -/* What flavor of waitpid/wait3/wait shall we use? */ - -#ifdef HAVE_WAITPID -# define WAIT(pid, statusp, options) waitpid(pid, statusp, options) -#else -# ifdef HAVE_WAIT3 -# define WAIT(pid, statusp, options) wait3((void *) statusp, options, NULL) -# else -# define WAIT(pid, statusp, options) wait(statusp) -# endif -#endif - /* the signal handler */ /**/ -RETSIGTYPE -handler(int sig) +mod_export RETSIGTYPE +zhandler(int sig) { sigset_t newmask, oldmask; @@ -414,13 +422,14 @@ handler(int sig) do_jump = suspend_longjmp; /* do we need to longjmp to signal_suspend */ suspend_longjmp = 0; /* In case a SIGCHLD somehow arrives */ - if (sig == SIGCHLD) { /* Traps can cause nested child_suspend() */ + if (sig == SIGCHLD) { /* Traps can cause nested signal_suspend() */ if (do_jump) jump_to = suspend_jmp_buf; /* Copy suspend_jmp_buf */ } #endif - if (queueing_enabled) { /* Are we queueing signals now? */ + /* Are we queueing signals now? */ + if (queueing_enabled) { int temp_rear = ++queue_rear % MAX_QUEUE_SIZE; DPUTS(temp_rear == queue_front, "BUG: signal queue full"); @@ -450,7 +459,25 @@ handler(int sig) int *procsubval = &cmdoutval; struct execstack *es = exstack; - pid = WAIT(-1, &status, WNOHANG|WUNTRACED); /* reap the child process */ + /* + * Reap the child process. + * If we want usage information, we need to use wait3. + */ +#ifdef HAVE_WAIT3 +# ifdef HAVE_GETRUSAGE + struct rusage ru; + + pid = wait3((void *)&status, WNOHANG|WUNTRACED, &ru); +# else + pid = wait3((void *)&status, WNOHANG|WUNTRACED, NULL); +# endif +#else +# ifdef HAVE_WAITPID + pid = waitpid(-1, &status, WNOHANG|WUNTRACED); +# else + pid = wait(&status); +# endif +#endif if (!pid) /* no more children to reap */ break; @@ -463,7 +490,7 @@ handler(int sig) *procsubval = (0200 | WTERMSIG(status)); else *procsubval = WEXITSTATUS(status); - times(&shtms); + get_usage(); goto cont; } if (!es) @@ -482,15 +509,25 @@ handler(int sig) } /* Find the process and job containing this pid and update it. */ - if (findproc(pid, &jn, &pn)) { + if (findproc(pid, &jn, &pn, 0)) { +#if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE) + struct timezone dummy_tz; + gettimeofday(&pn->endtime, &dummy_tz); + pn->status = status; + pn->ti = ru; +#else update_process(pn, status); +#endif + update_job(jn); + } else if (findproc(pid, &jn, &pn, 1)) { + pn->status = status; update_job(jn); } else { /* If not found, update the shell record of time spent by * children in sub processes anyway: otherwise, this * will get added on to the next found process that terminates. */ - times(&shtms); + get_usage(); } } break; @@ -541,8 +578,7 @@ handler(int sig) alarm(tmout - idle); else { errflag = noerrs = 0; - zerr("timeout", NULL, 0); - errflag = 0; + zwarn("timeout", NULL, 0); stopmsg = 1; zexit(SIGALRM, 1); } @@ -575,7 +611,7 @@ killrunjobs(int from_signal) if (unset(HUP)) return; - for (i = 1; i < MAXJOB; i++) + for (i = 1; i <= maxjob; i++) if ((from_signal || i != thisjob) && (jobtab[i].stat & STAT_LOCKED) && !(jobtab[i].stat & STAT_NOPRINT) && !(jobtab[i].stat & STAT_STOPPED)) { @@ -642,9 +678,11 @@ struct savetrap { }; static LinkList savetraps; +static int dontsavetrap; /* - * Save the current trap and unset it. + * Save the current trap by copying it. This does nothing to + * the existing value of sigtrapped or sigfuncs. */ static void @@ -659,16 +697,25 @@ dosavetrap(int sig, int level) * Get the old function: this assumes we haven't added * the new one yet. */ - char func[20]; - sprintf(func, "TRAP%s", sigs[sig]); - /* We call removehashnode() directly because otherwise - * removeshfuncnode() would be called which in turn would - * call us again so that we would end up with a NULL pointer - * instead of the list for the trap. */ - st->list = removehashnode(shfunctab, func); + Shfunc shf, newshf = NULL; + if ((shf = (Shfunc)gettrapnode(sig, 1))) { + /* Copy the node for saving */ + newshf = (Shfunc) zalloc(sizeof(*newshf)); + newshf->nam = ztrdup(shf->nam); + newshf->flags = shf->flags; + newshf->funcdef = dupeprog(shf->funcdef, 0); + if (shf->flags & PM_UNDEFINED) + newshf->funcdef->shf = newshf; + } +#ifdef DEBUG + else dputs("BUG: no function present with function trap flag set."); +#endif + st->list = newshf; + } else if (sigtrapped[sig]) { + st->list = sigfuncs[sig] ? dupeprog(sigfuncs[sig], 0) : NULL; } else { - st->list = sigfuncs[sig]; - sigfuncs[sig] = NULL; + DPUTS(sigfuncs[sig], "BUG: sigfuncs not null for untrapped signal"); + st->list = NULL; } if (!savetraps) savetraps = znewlinklist(); @@ -693,10 +740,11 @@ settrap(int sig, Eprog l) * Call unsettrap() unconditionally, to make sure trap is saved * if necessary. */ + queue_signals(); unsettrap(sig); sigfuncs[sig] = l; - if (!l) { + if (empty_eprog(l)) { sigtrapped[sig] = ZSIG_IGNORED; if (sig && sig <= SIGCOUNT && #ifdef SIGWINCH @@ -705,6 +753,7 @@ settrap(int sig, Eprog l) sig != SIGCHLD) signal_ignore(sig); } else { + nsigtrapped++; sigtrapped[sig] = ZSIG_TRAPPED; if (sig && sig <= SIGCOUNT && #ifdef SIGWINCH @@ -719,32 +768,51 @@ settrap(int sig, Eprog l) * works just the same. */ sigtrapped[sig] |= (locallevel << ZSIG_SHIFT); + unqueue_signals(); return 0; } /**/ void unsettrap(int sig) +{ + HashNode hn; + + queue_signals(); + hn = removetrap(sig); + if (hn) + shfunctab->freenode(hn); + unqueue_signals(); +} + +/**/ +HashNode +removetrap(int sig) { int trapped; if (sig == -1 || (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN))) - return; + return NULL; + queue_signals(); trapped = sigtrapped[sig]; /* * Note that we save the trap here even if there isn't an existing * one, to aid in removing this one. However, if there's * already one at the current locallevel we just overwrite it. */ - if (isset(LOCALTRAPS) && locallevel && + if (!dontsavetrap && (isset(LOCALTRAPS) || sig == SIGEXIT) && + locallevel && (!trapped || locallevel > (sigtrapped[sig] >> ZSIG_SHIFT))) dosavetrap(sig, locallevel); - if (!trapped) - return; - + if (!trapped) { + unqueue_signals(); + return NULL; + } + if (sigtrapped[sig] & ZSIG_TRAPPED) + nsigtrapped--; sigtrapped[sig] = 0; if (sig == SIGINT && interact) { /* PWS 1995/05/16: added test for interactive, also noholdintr() * @@ -762,26 +830,32 @@ unsettrap(int sig) /* * At this point we free the appropriate structs. If we don't - * want that to happen (e.g. we are saving the trap), then - * either the function should already have been removed from shfunctab, - * or the entry in sigfuncs should have been set to NULL, and then - * we're laughing, in a sort of vague virtual sense. + * want that to happen then either the function should already have been + * removed from shfunctab, or the entry in sigfuncs should have been set + * to NULL. This is no longer necessary for saving traps as that + * copies the structures, so here we are remove the originals. + * That causes a little inefficiency, but a good deal more reliability. */ if (trapped & ZSIG_FUNC) { - char func[20]; - HashNode hn; + HashNode node = gettrapnode(sig, 1); - sprintf(func, "TRAP%s", sigs[sig]); /* * As in dosavetrap(), don't call removeshfuncnode() because * that calls back into unsettrap(); */ - if ((hn = removehashnode(shfunctab, func))) - shfunctab->freenode(hn); + sigfuncs[sig] = NULL; + if (node) + removehashnode(shfunctab, node->nam); + unqueue_signals(); + + return node; } else if (sigfuncs[sig]) { freeeprog(sigfuncs[sig]); sigfuncs[sig] = NULL; } + unqueue_signals(); + + return NULL; } /**/ @@ -823,9 +897,11 @@ endtrapscope(void) exitfn = removehashnode(shfunctab, "TRAPEXIT"); } else { exitfn = sigfuncs[SIGEXIT]; - sigfuncs[SIGEXIT] = NULL; } - unsettrap(SIGEXIT); + sigfuncs[SIGEXIT] = NULL; + if (sigtrapped[SIGEXIT] & ZSIG_TRAPPED) + nsigtrapped--; + sigtrapped[SIGEXIT] = 0; } if (savetraps) { @@ -836,21 +912,25 @@ endtrapscope(void) remnode(savetraps, ln); - if (sigtrapped[sig]) - unsettrap(sig); - sigtrapped[sig] = st->flags; - if (st->flags) { + if (st->flags && (st->list != NULL)) { Eprog prog = (st->flags & ZSIG_FUNC) ? ((Shfunc) st->list)->funcdef : (Eprog) st->list; /* prevent settrap from saving this */ - int oldlt = opts[LOCALTRAPS]; - opts[LOCALTRAPS] = 0; + dontsavetrap++; settrap(sig, prog); - opts[LOCALTRAPS] = oldlt; + dontsavetrap--; + /* + * counting of nsigtrapped should presumably be handled + * in settrap... + */ + DPUTS((sigtrapped[sig] ^ st->flags) & ZSIG_TRAPPED, + "BUG: settrap didn't restore correct ZSIG_TRAPPED"); if ((sigtrapped[sig] = st->flags) & ZSIG_FUNC) shfunctab->addnode(shfunctab, ((Shfunc)st->list)->nam, (Shfunc) st->list); - } + } else if (sigtrapped[sig]) + unsettrap(sig); + zfree(st, sizeof(*st)); } } @@ -863,12 +943,23 @@ endtrapscope(void) else freeeprog(exitfn); } + DPUTS(!locallevel && savetraps && firstnode(savetraps), + "BUG: still saved traps outside all function scope"); } /* Execute a trap function for a given signal, possibly * with non-standard sigtrapped & sigfuncs values */ +/* Are we already executing a trap? */ +/**/ +int intrap; + +/* Is the current trap a function? */ + +/**/ +int trapisfunc; + /**/ void dotrapargs(int sig, int *sigtr, void *sigfn) @@ -877,7 +968,8 @@ dotrapargs(int sig, int *sigtr, void *sigfn) char *name, num[4]; int trapret = 0; int obreaks = breaks; - + int isfunc; + /* if signal is being ignored or the trap function * * is NULL, then return * * * @@ -885,10 +977,30 @@ dotrapargs(int sig, int *sigtr, void *sigfn) * function will test for this, but this way we keep status flags * * intact without working too hard. Special cases (e.g. calling * * a trap for SIGINT after the error flag was set) are handled * - * by the calling code. (PWS 1995/06/08). */ + * by the calling code. (PWS 1995/06/08). * + * * + * This test is now replicated in dotrap(). */ if ((*sigtr & ZSIG_IGNORED) || !sigfn || errflag) return; + /* + * Never execute special (synchronous) traps inside other traps. + * This can cause unexpected code execution when more than one + * of these is set. + * + * The down side is that it's harder to debug traps. I don't think + * that's a big issue. + */ + if (intrap) { + switch (sig) { + case SIGEXIT: + case SIGDEBUG: + case SIGZERR: + return; + } + } + + intrap++; *sigtr |= ZSIG_IGNORED; lexsave(); @@ -897,49 +1009,99 @@ dotrapargs(int sig, int *sigtr, void *sigfn) runhookdef(BEFORETRAPHOOK, NULL); if (*sigtr & ZSIG_FUNC) { int osc = sfcontext; + HashNode hn = gettrapnode(sig, 0); args = znewlinklist(); - name = (char *) zalloc(5 + strlen(sigs[sig])); - sprintf(name, "TRAP%s", sigs[sig]); + /* + * In case of multiple names, try to get + * a hint of the name in use from the function table. + * In special cases, e.g. EXIT traps, the function + * has already been removed. Then it's OK to + * use the standard name. + */ + if (hn) { + name = ztrdup(hn->nam); + } else { + name = (char *) zalloc(5 + strlen(sigs[sig])); + sprintf(name, "TRAP%s", sigs[sig]); + } zaddlinknode(args, name); sprintf(num, "%d", sig); zaddlinknode(args, num); - trapreturn = -1; + trapreturn = -1; /* incremented by doshfunc */ + trapisfunc = isfunc = 1; + sfcontext = SFC_SIGNAL; doshfunc(name, sigfn, args, 0, 1); sfcontext = osc; freelinklist(args, (FreeFunc) NULL); zsfree(name); - } else + + } else { + trapreturn = -2; /* not incremented, used at current level */ + trapisfunc = isfunc = 0; + execode(sigfn, 1, 0); + } runhookdef(AFTERTRAPHOOK, NULL); - if (trapreturn > 0) + if (trapreturn > 0 && isfunc) { + /* + * Context was its own function. We propagate the return + * value specially. Return value zero means don't do + * anything special, so don't handle it. + */ trapret = trapreturn; - else if (errflag) + } else if (trapreturn >= 0 && !isfunc) { + /* + * Context was an inline trap. If an explicit return value + * was used, we need to set `lastval'. Otherwise we use the + * value restored by execrestore. In this case, all return + * values indicate an explicit return from the current function, + * so always handle specially. trapreturn is always restored by + * execrestore. + */ + trapret = trapreturn + 1; + } else if (errflag) trapret = 1; execrestore(); lexrestore(); if (trapret > 0) { - breaks = loops; - errflag = 1; + if (isfunc) { + breaks = loops; + errflag = 1; + } else { + lastval = trapret-1; + } } else { breaks += obreaks; if (breaks > loops) breaks = loops; } + /* + * If zle was running while the trap was executed, see if we + * need to restore the display. + */ + if (zleactive && resetneeded) + zrefresh(); + if (*sigtr != ZSIG_IGNORED) *sigtr &= ~ZSIG_IGNORED; + intrap--; } -/* Standard call to execute a trap for a given signal */ +/* Standard call to execute a trap for a given signal. */ /**/ void dotrap(int sig) { + /* Copied from dotrapargs(). */ + if ((sigtrapped[sig] & ZSIG_IGNORED) || !sigfuncs[sig] || errflag) + return; + dotrapargs(sig, sigtrapped+sig, sigfuncs[sig]); } diff --git a/Test/C03traps.ztst b/Test/C03traps.ztst new file mode 100644 index 000000000..f75c47c4d --- /dev/null +++ b/Test/C03traps.ztst @@ -0,0 +1,251 @@ +# Tests for both trap builtin and TRAP* functions. + +%prep + + setopt localtraps + mkdir traps.tmp && cd traps.tmp + +%test + + fn1() { + trap 'print EXIT1' EXIT + fn2() { trap 'print EXIT2' EXIT; } + fn2 + } + fn1 +0:Nested `trap ... EXIT' +>EXIT2 +>EXIT1 + + fn1() { + TRAPEXIT() { print EXIT1; } + fn2() { TRAPEXIT() { print EXIT2; }; } + fn2 + } + fn1 +0: Nested TRAPEXIT +>EXIT2 +>EXIT1 + + fn1() { + trap 'print EXIT1' EXIT + fn2() { trap - EXIT; } + fn2 + } + fn1 +0:Nested `trap - EXIT' on `trap ... EXIT' +>EXIT1 + + fn1() { + TRAPEXIT() { print EXIT1; } + fn2() { trap - EXIT; } + fn2 + } + fn1 +0:Nested `trap - EXIT' on `TRAPEXIT' +>EXIT1 + +# We can't test an EXIT trap for the shell as a whole, because +# we're inside a function scope which we don't leave when the +# subshell exits. Not sure if that's the correct behaviour, but +# it's sort of consistent. + ( fn1() { trap 'print Function 1 going' EXIT; exit; print Not reached; } + fn2() { trap 'print Function 2 going' EXIT; fn1; print Not reached; } + fn2 + ) +0:EXIT traps on functions when exiting from function +>Function 1 going +>Function 2 going + +# $ZTST_exe is relative to the parent directory. +# We ought to fix this in ztst.zsh... + cd .. + $ZTST_exe -fc 'TRAPEXIT() { print Exited.; }' +0:EXIT traps on a script +>Exited. + + fn1() { + trap + trap 'print INT1' INT + fn2() { trap 'print INT2' INT; trap; } + trap + fn2 + trap + } + fn1 +0: Nested `trap ... INT', not triggered +>trap -- 'print INT1' INT +>trap -- 'print INT2' INT +>trap -- 'print INT1' INT + + fn1() { + trap + TRAPINT() { print INT1; } + fn2() { TRAPINT() { print INT2; }; trap; } + trap + fn2 + trap + } + fn1 +0: Nested `trap ... INT', not triggered +>TRAPINT () { +> print INT1 +>} +>TRAPINT () { +> print INT2 +>} +>TRAPINT () { +> print INT1 +>} + + fn1() { + trap 'print INT1' INT + fn2() { trap - INT; trap; } + trap + fn2 + trap + } + fn1 +0: Nested `trap - INT' on untriggered `trap ... INT' +>trap -- 'print INT1' INT +>trap -- 'print INT1' INT + +# Testing the triggering of traps here is very unpleasant. +# The delays are attempts to avoid race conditions, though there is +# no guarantee that they will work. Note the subtlety that the +# `sleep' in the function which receives the trap does *not* get the +# signal, only the parent shell, which is waiting for a SIGCHILD. +# (At least, that's what I think is happening.) Thus we have to wait at +# least the full two seconds to make sure we have got the output from the +# execution of the trap. + + print 'This test takes at least three seconds...' >&8 + fn1() { + trap 'print TERM1' TERM + fn2() { trap 'print TERM2; return 1' TERM; sleep 2; } + fn2 & + sleep 1 + kill -TERM $! + sleep 2 + } + fn1 +0: Nested `trap ... TERM', triggered on inner loop +>TERM2 + + print 'This test, too, takes at least three seconds...' >&8 + fn1() { + trap 'print TERM1; return 1' TERM + fn2() { trap 'print TERM2; return 1' TERM; } + fn2 + sleep 2 + } + fn1 & + sleep 1 + kill -TERM $! + sleep 2 +0: Nested `trap ... TERM', triggered on outer loop +>TERM1 + + TRAPZERR() { print error activated; } + fn() { print start of fn; false; print end of fn; } + fn + fn() { + setopt localoptions localtraps + unfunction TRAPZERR + print start of fn + false + print end of fn + } + fn + unfunction TRAPZERR + print finish +0: basic localtraps handling +>start of fn +>error activated +>end of fn +>start of fn +>end of fn +>finish + + TRAPZERR() { print 'ERR-or!'; } + f() { print f; false; } + t() { print t; } + f + f && t + t && f && true + t && f + testunset() { + setopt localtraps + unset -f TRAPZERR + print testunset + false + true + } + testunset + f + print status $? + unfunction TRAPZERR +0: more sophisticated error trapping +>f +>ERR-or! +>f +>t +>f +>t +>f +>ERR-or! +>testunset +>f +>ERR-or! +>status 1 + + f() { + setopt localtraps + TRAPWINCH() { print "Window changed. That wrecked the test."; } + } + f + f + functions TRAPWINCH +1:Unsetting ordinary traps with localtraps. + +# +# Returns from within traps are a perennial problem. +# The following two apply to returns in and around standard +# ksh-style traps. The intention is that a return value from +# within the function is preserved (i.e. statuses set by the trap +# are ignored) unless the trap explicitly executes `return', which makes +# it return from the enclosing function. +# + fn() { trap 'true' EXIT; return 1; } + fn +1: ksh-style EXIT traps preserve return value + + inner() { trap 'return 3' EXIT; return 2; } + outer() { inner; return 1; } + outer +3: ksh-style EXIT traps can force return status of enclosing function + +# Autoloaded traps are horrid, but unfortunately people expect +# them to work if we support them. + echo "print Running exit trap" >TRAPEXIT + $ZTST_testdir/../Src/zsh -fc ' + fpath=(. $fpath) + autoload TRAPEXIT + print "Exiting, attempt 1" + exit + print "What?" + ' + $ZTST_testdir/../Src/zsh -fc ' + fpath=(. $fpath) + autoload TRAPEXIT; + fn() { print Some function } + fn + print "Exiting, attempt 2" + exit + ' +0: autoloaded TRAPEXIT (exit status > 128 indicates an old bug is back) +>Exiting, attempt 1 +>Running exit trap +>Some function +>Exiting, attempt 2 +>Running exit trap -- cgit 1.4.1