mirror of
https://github.com/neovim/neovim.git
synced 2026-08-27 17:41:48 +00:00
fix(api): nvim_get_option_value dummy buffer crashes
Problem: nvim_get_option_value with "filetype" set can crash if autocommands open the dummy buffer in more windows, or if &bufhidden == "wipe". Solution: Attempt to close all dummy buffer windows before wiping. Promote the dummy buffer to a normal buffer if that fails.
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
#include "nvim/types_defs.h"
|
#include "nvim/types_defs.h"
|
||||||
#include "nvim/vim_defs.h"
|
#include "nvim/vim_defs.h"
|
||||||
|
#include "nvim/window.h"
|
||||||
|
|
||||||
#include "api/options.c.generated.h"
|
#include "api/options.c.generated.h"
|
||||||
|
|
||||||
@@ -149,6 +150,27 @@ static buf_T *do_ft_buf(const char *filetype, aco_save_T *aco, bool *aco_used, E
|
|||||||
return ftbuf;
|
return ftbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wipe_ft_buf(buf_T *buf)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
block_autocmds();
|
||||||
|
|
||||||
|
bufref_T bufref;
|
||||||
|
set_bufref(&bufref, buf);
|
||||||
|
|
||||||
|
close_windows(buf, false);
|
||||||
|
// Autocommands are blocked, but 'bufhidden' may have wiped it already.
|
||||||
|
// Also can't wipe if the buffer is somehow still in a window or current.
|
||||||
|
if (bufref_valid(&bufref) && buf != curbuf && buf->b_nwindows == 0) {
|
||||||
|
wipe_buffer(buf, false);
|
||||||
|
}
|
||||||
|
if (bufref_valid(&bufref)) {
|
||||||
|
buf->b_flags &= ~BF_DUMMY; // Couldn't wipe; keep it instead.
|
||||||
|
}
|
||||||
|
|
||||||
|
unblock_autocmds();
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the value of an option. The behavior of this function matches that of
|
/// Gets the value of an option. The behavior of this function matches that of
|
||||||
/// |:set|: the local value of an option is returned if it exists; otherwise,
|
/// |:set|: the local value of an option is returned if it exists; otherwise,
|
||||||
/// the global value is returned. Local values always correspond to the current
|
/// the global value is returned. Local values always correspond to the current
|
||||||
@@ -191,10 +213,8 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
|||||||
aucmd_restbuf(&aco);
|
aucmd_restbuf(&aco);
|
||||||
}
|
}
|
||||||
if (ftbuf != NULL) {
|
if (ftbuf != NULL) {
|
||||||
assert(curbuf != ftbuf); // safety check
|
wipe_ft_buf(ftbuf);
|
||||||
wipe_buffer(ftbuf, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Object)OBJECT_INIT;
|
return (Object)OBJECT_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,8 +230,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
|||||||
// restore curwin/curbuf and a few other things
|
// restore curwin/curbuf and a few other things
|
||||||
aucmd_restbuf(&aco);
|
aucmd_restbuf(&aco);
|
||||||
}
|
}
|
||||||
assert(curbuf != ftbuf); // safety check
|
wipe_ft_buf(ftbuf);
|
||||||
wipe_buffer(ftbuf, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ERROR_SET(err)) {
|
if (ERROR_SET(err)) {
|
||||||
|
|||||||
@@ -1995,6 +1995,46 @@ describe('API', function()
|
|||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('does not crash if autocmds open dummy buffer in other windows', function()
|
||||||
|
exec [[
|
||||||
|
autocmd FileType * ++once let g:dummy_buf = bufnr() | split
|
||||||
|
|
||||||
|
" Autocommands should be blocked while Nvim attempts to wipe the buffer.
|
||||||
|
let g:wipe_events = []
|
||||||
|
autocmd WinClosed * if winbufnr(expand('<amatch>')) == g:dummy_buf
|
||||||
|
\| let g:wipe_events += ['WinClosed']
|
||||||
|
\| endif
|
||||||
|
autocmd BufWipeout * if expand('<abuf>') == g:dummy_buf
|
||||||
|
\| let g:wipe_events += ['BufWipeout']
|
||||||
|
\| endif
|
||||||
|
]]
|
||||||
|
api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
|
||||||
|
eq(0, eval('bufexists(g:dummy_buf)'))
|
||||||
|
eq({}, eval('win_findbuf(g:dummy_buf)'))
|
||||||
|
eq({}, eval('g:wipe_events'))
|
||||||
|
|
||||||
|
-- Be an ABSOLUTE nuisance and make it the only window to prevent it from wiping.
|
||||||
|
-- Do it this way to avoid E813 from :only trying to close the autocmd window.
|
||||||
|
command('autocmd FileType * ++once let g:dummy_buf = bufnr() | split | wincmd w | quit')
|
||||||
|
api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
|
||||||
|
eq(1, eval('bufexists(g:dummy_buf)'))
|
||||||
|
|
||||||
|
-- Ensure the buffer does not remain as a dummy by checking that we can switch to it.
|
||||||
|
local old_win = api.nvim_get_current_win()
|
||||||
|
command('execute g:dummy_buf "sbuffer"')
|
||||||
|
eq(eval('g:dummy_buf'), api.nvim_get_current_buf())
|
||||||
|
neq(old_win, api.nvim_get_current_win())
|
||||||
|
eq({}, eval('g:wipe_events'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not crash if dummy buffer wiped after autocommands', function()
|
||||||
|
-- Autocommands are blocked while Nvim attempts to wipe the buffer, but check something like
|
||||||
|
-- &bufhidden = "wipe" causing a premature wipe doesn't crash.
|
||||||
|
command('autocmd FileType * ++once setlocal bufhidden=wipe | split')
|
||||||
|
api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
|
||||||
|
assert_alive()
|
||||||
|
end)
|
||||||
|
|
||||||
it('sets dummy buffer options without side-effects', function()
|
it('sets dummy buffer options without side-effects', function()
|
||||||
exec [[
|
exec [[
|
||||||
let g:events = []
|
let g:events = []
|
||||||
|
|||||||
Reference in New Issue
Block a user