Subject: Re: Segfault on completion with interactive mode
From: Bart Schaefer <[email-masked]>
To: [email-masked]
Date: Wed, 10 Feb 2016 20:01:47 -0800
List-Id: Zsh Workers List <zsh-workers.zsh.org>

[ Long diagnosis follows; read just the first and last paragraphs for
  the short version. ]

I think the problem is that "compadd -x" (which is used to show the
warnings) updates the current group of completions to have an empty
match with the warning as its explanation string.  Because interactive
mode is doing its own loop on getkeycmd(), the listing data is not
restored after "compadd -x" clobbers it, and so the next time around
the interactive mode loop, its idea of what is in the set of matches
does not correspond, and chaos ensues.

It comes down to this block of code in domenuselect():

		if (nmessages) {
		    showinglist = -2;
		    zrefresh();
		} else {
		    trashzle();
		    zsetterm();
		    if (tccan(TCCLEAREOD))
			tcout(TCCLEAREOD);
		    fputs("no matches\r", shout);
		    fflush(shout);
		    tcmultout(TCUP, TCMULTUP, nlnct);
		    showinglist = clearlist = 0;
		    clearflag = 1;
		    zrefresh();
		    showinglist = clearlist = 0;
		}
		statusline = NULL;

		goto getk;

And earlier:

    getk:

    	if (!do_last_key) {
	    zmult = 1;
	    cmd = getkeycmd();
	    /*
	     * On interrupt, we'll exit due to cmd being empty.
	     * Don't propagate the interrupt any further, which
	     * can screw up redrawing.
	     */
	    errflag &= ~ERRFLAG_INT;
	    if (mtab_been_reallocated) {
		do_last_key = 1;
		continue;
	    }
    	}
	do_last_key = 0;

When (nmessages) is true, we jump to getk:, do_last_key is false so
we arrive at getkeycmd(), and mtab_been_reallocated is true so after
reading the delete-char we continue back to the top of the "for (;;)"
loop and finally crash inside the call to zrefresh() at line 2550 --
which I *think* is intended to redraw the old list before highlighing
the desired item within it, but the old list has been replaced by the
warning message.

If there are no matches and (nmessages == 0), we go through the other
branch [beginning with trashzle()].  Thereafter everything is the
same except that we successfully redraw the list in zrefresh() and
go on as we were.

If showinglist is set to anything other than -2 in the (nmessages)
true branch, there's no crash but the messages aren't shown either.
If the list is invalidated (force listdat.valid = 0) then there also
isn't a crash but menu selection exits.

The thing is, I think domenuselect() has pushed all the right things
onto its linked list of Menustack structures, it just hasn't ever
restored it again.  But I'm not sure and I haven't figured out the
right place to restore it if so.  Other eyeballs?