Hi.

I found a bug in buffer unloading algorithm due to kind of
misconception between how close_buffer() treats buffer unloading data
(checking first character in b_p_bl[] in passed buffer) and situations
when this function can be called. The bug is triggered in rare cases
when

1). a user makes ':q' from a latest window in a tabpage which has
bufhidden=delete (or :cclose from a normal buffer, see later) and at
the same time
2). specific BufDelete autocommands that temporarily load other
buffer(s) as curbuf are triggered.

This is how to reproduce it:

1). put in .vimrc:

set switchbuf=usetab
autocmd BufEnter * if empty(&buftype) | setlocal bufhidden=delete |
endif

this will make all normal buffers marked as bufhidden=delete. I use
this for easy navigation between open buffers using arrow keys mapped
as

nmap <C-left>   :tabp<CR>
nmap <C-right>  :tabn<CR>
nmap <C-up>     :sbn<CR>
nmap <C-down>   :sbp<CR>

and bufhidden=delete won't raise up already hidden buffers

2) install dbext plugin. It just has excellent BufDelete autocommand
which temporarily swaps curbuf to its own buffer and then return
curbuf to previous buffer. Actually dbext made it possible to find
this vim's issue.

3) Now open some file, when loaded do

:tabnew <other-file>

you will be switched to a new tabpage and there do

:q

You will be returned to the original file, but... Now there is no
syntax highlight and ':ls' will show that there is no current buffer!
':ls!' will show that the current buffer is unloaded. Same behaviour
can be found when using quickfix window instead tabpage.


What is going on? I spent a day time to find the answer.

When ':q' is called at the only window in a tabpage function ex_quit()
is called, then following code flow takes place:

win_close() -> win_close_othertab() -> close_buffer() -> buf_freeall()
->

    if ((flags & BFA_DEL) && buf->b_p_bl)
    {
        apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, FALSE,
buf);
        if (!buf_valid(buf))        /* autocommands may delete the buffer */
            return;
    }

in the close_buffer() curent buffer is going to be closed and deleted
(as it is marked as bufhidden=delete) - it is OK. But deeper we are
going to execute autocommands for EVENT_BUFDELETE. Those autocommands
are allowed to change global variable curbuf. Then let's see what
dbext's autocommand do:

function! dbext#DB_auVimLeavePre() "{{{
    " Loop through all buffers
    " Disconnect if the buffer has a DBI or ODBC connection
    " Remove any dictionary files (if created)

    " Save the current buffer to switch back to
    let cur_buf = bufnr("%")

    for buf_nbr in s:dbext_buffers_with_dict_files
        " Switch to the buffer being deleted
        silent! exec buf_nbr.'buffer'

        call s:DB_DictionaryDelete( 'Table' )
        call s:DB_DictionaryDelete( 'Procedure' )
        call s:DB_DictionaryDelete( 'View' )
    endfor

    if exists('g:loaded_dbext_dbi')
        perl db_disconnect_all()
    endif

    if s:DB_get("delete_temp_file") == 1
        let rc = delete(s:dbext_tempfile)
    endif

    " Switch back to the current buffer
    silent! exec cur_buf.'buffer'
endfunction "}}}

So it temporarily change curbuf using ':[N]buffer' command and then
retutns it back. This is code flow done when the first ':[N]buffer'
will be called:

ex_buffer() -> goto_buffer() -> do_buffer() -> set_curbuf(with
action=DOBUF_GOTO) -> close_buffer()

this last close_buffer() has semantics different from the former
close_buffer() called when ':q' was pressed! This close_buffer() just
wants to temporarily hide current buffer (a window that we must enter
after closing last buffer in 2nd tabpage). We are going to revert this
buffer back after 2nd '[N]buffer' in dbexts' autocommand. But it will
not be possible because close_buffer() just unloaded it! Why? Because,
remember, that we use bufhidden=delete for all normal windows.

close_buffer() does not make difference between situations where it
was called. It just simply checks that first character of b_p_bh[] of
passed buffer is 'd' (delete) or 'w' (wipe). And buffer where we must
jump after closing 2nd tabpage will be delete just because of its
internal data. That's why i call it misconception bug.


I made a simple patch that just temporarily puts '\0' in b_p_bh[0]
before calling to close_buffer() from set_curbuf() and then revert the
first character back. It works fine but I think that a better way is
to add a parameter to close_buf() that will make this function aware
about aim it was called for. As i do not know how to attach files i
put it just here:


diff -r 379a6398d462 src/buffer.c
--- a/src/buffer.c      Wed Oct 26 23:48:21 2011 +0200
+++ b/src/buffer.c      Mon Nov 14 18:26:12 2011 +0300
@@ -1370,12 +1370,16 @@
        if (buf_valid(prevbuf))
 #endif
        {
+           u_char  fstchar = prevbuf->b_p_bh[0];
+
            if (prevbuf == curbuf)
                u_sync(FALSE);
+           prevbuf->b_p_bh[0] = '\0';
            close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL,
prevbuf,
                    unload ? action : (action == DOBUF_GOTO
                        && !P_HID(prevbuf)
                        && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0);
+           prevbuf->b_p_bh[0] = fstchar;
        }
     }
 #ifdef FEAT_AUTOCMD
diff -r 379a6398d462 src/window.c
--- a/src/window.c      Wed Oct 26 23:48:21 2011 +0200
+++ b/src/window.c      Mon Nov 14 18:26:12 2011 +0300
@@ -2309,7 +2309,7 @@

 /*
  * Close window "win" in tab page "tp", which is not the current tab
page.
- * This may be the last window ih that tab page and result in closing
the tab,
+ * This may be the last window in that tab page and result in closing
the tab,
  * thus "tp" may become invalid!
  * Caller must check if buffer is hidden and whether the tabline
needs to be
  * updated.


Yes, second file is just fixing a typo.

I do not know if it is effective to report bugs in this maillist. But
I did not find other bug reports mechanism in vim.


Cheers, Alexey.

-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

Raspunde prin e-mail lui