term: ensure term->buf is valid

The fallowing test (reduced), submitted by @mhinz may free term->buf,
leaving the pointer dangling.

```vim
let s:buf  = -1

function! s:exit_handler()
  execute 'bdelete!' s:buf
endfunction

vnew
let s:buf = bufnr('%')
let id = termopen('sleep 1', { 'on_exit': function('s:exit_handler') })

call s:test()
```

When the buffer is known to be closing, set term->buf to NULL, and check
buf_valid() in on_refresh().

Helped-by: Marco Hinz (@mhinz)
This commit is contained in:
Scott Prager 2015-04-06 17:47:08 -04:00
parent 013bd4461d
commit 8cac2eea75

View File

@ -307,9 +307,12 @@ void terminal_close(Terminal *term, char *msg)
term->forward_mouse = false; term->forward_mouse = false;
term->closed = true; term->closed = true;
if (!msg || exiting) { if (!msg || exiting) {
// If no msg was given, this was called by close_buffer(buffer.c) so we // If no msg was given, this was called by close_buffer(buffer.c). Or if
// should not wait for the user to press a key. Also cannot wait if // exiting, we must inform the buffer the terminal no longer exists so that
// `exiting == true` // close_buffer() doesn't call this again.
term->buf->terminal = NULL;
term->buf = NULL;
// We should not wait for the user to press a key.
term->opts.close_cb(term->opts.data); term->opts.close_cb(term->opts.data);
} else { } else {
terminal_receive(term, msg, strlen(msg)); terminal_receive(term, msg, strlen(msg));
@ -451,7 +454,9 @@ end:
void terminal_destroy(Terminal *term) void terminal_destroy(Terminal *term)
{ {
term->buf->terminal = NULL; if (term->buf) {
term->buf->terminal = NULL;
}
term->buf = NULL; term->buf = NULL;
pmap_del(ptr_t)(invalidated_terminals, term); pmap_del(ptr_t)(invalidated_terminals, term);
for (size_t i = 0 ; i < term->sb_current; i++) { for (size_t i = 0 ; i < term->sb_current; i++) {
@ -930,8 +935,13 @@ static void on_refresh(Event event)
// be used act on terminal output. // be used act on terminal output.
block_autocmds(); block_autocmds();
map_foreach(invalidated_terminals, term, stub, { map_foreach(invalidated_terminals, term, stub, {
if (!term->buf) { // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid.
bool valid = true;
if (!term->buf || !(valid = buf_valid(term->buf))) {
// destroyed by `close_buffer`. Dont do anything else // destroyed by `close_buffer`. Dont do anything else
if (!valid) {
term->buf = NULL;
}
continue; continue;
} }
bool pending_resize = term->pending_resize; bool pending_resize = term->pending_resize;