mirror of
https://github.com/neovim/neovim.git
synced 2026-02-21 09:50:19 +10: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.
(cherry picked from commit 7e2e116343)
This commit is contained in:
committed by
github-actions[bot]
parent
b0f341feea
commit
10a1df2789
@@ -19,6 +19,7 @@
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/types_defs.h"
|
||||
#include "nvim/vim_defs.h"
|
||||
#include "nvim/window.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "api/options.c.generated.h"
|
||||
@@ -151,6 +152,27 @@ static buf_T *do_ft_buf(const char *filetype, aco_save_T *aco, bool *aco_used, E
|
||||
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
|
||||
/// |: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
|
||||
@@ -193,10 +215,8 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
||||
aucmd_restbuf(&aco);
|
||||
}
|
||||
if (ftbuf != NULL) {
|
||||
assert(curbuf != ftbuf); // safety check
|
||||
wipe_buffer(ftbuf, false);
|
||||
wipe_ft_buf(ftbuf);
|
||||
}
|
||||
|
||||
return (Object)OBJECT_INIT;
|
||||
}
|
||||
|
||||
@@ -212,8 +232,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
||||
// restore curwin/curbuf and a few other things
|
||||
aucmd_restbuf(&aco);
|
||||
}
|
||||
assert(curbuf != ftbuf); // safety check
|
||||
wipe_buffer(ftbuf, false);
|
||||
wipe_ft_buf(ftbuf);
|
||||
}
|
||||
|
||||
if (ERROR_SET(err)) {
|
||||
|
||||
@@ -2008,6 +2008,46 @@ describe('API', function()
|
||||
)
|
||||
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()
|
||||
exec [[
|
||||
let g:events = []
|
||||
|
||||
Reference in New Issue
Block a user