mirror of
https://github.com/neovim/neovim.git
synced 2026-08-28 10:01:49 +00:00
fix(api): autocmds mess up nvim_get_option_value's dummy buffer
Problem: When the "filetype" key is set for nvim_get_option_value, autocommands can crash Nvim by prematurely wiping the dummy buffer, or cause options intended for it to instead be set for unrelated buffers if switched during OptionSet. Solution: Don't crash. Also quash side-effects from setting the buffer options.
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/buffer_defs.h"
|
#include "nvim/buffer_defs.h"
|
||||||
#include "nvim/globals.h"
|
#include "nvim/globals.h"
|
||||||
|
#include "nvim/memline.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/memory_defs.h"
|
#include "nvim/memory_defs.h"
|
||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
@@ -99,8 +100,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a dummy buffer and run the FileType autocmd on it.
|
/// Create a dummy buffer and run the FileType autocmd on it.
|
||||||
static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
|
static buf_T *do_ft_buf(const char *filetype, aco_save_T *aco, bool *aco_used, Error *err)
|
||||||
|
FUNC_ATTR_NONNULL_ARG(2, 3, 4)
|
||||||
{
|
{
|
||||||
|
*aco_used = false;
|
||||||
if (filetype == NULL) {
|
if (filetype == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -112,19 +115,37 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open a memline for use by autocommands.
|
||||||
|
if (ml_open(ftbuf) == FAIL) {
|
||||||
|
api_set_error(err, kErrorTypeException, "Could not load internal buffer");
|
||||||
|
return ftbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufref_T bufref;
|
||||||
|
set_bufref(&bufref, ftbuf);
|
||||||
|
|
||||||
// Set curwin/curbuf to buf and save a few things.
|
// Set curwin/curbuf to buf and save a few things.
|
||||||
aucmd_prepbuf(aco, ftbuf);
|
aucmd_prepbuf(aco, ftbuf);
|
||||||
|
*aco_used = true;
|
||||||
|
|
||||||
|
set_option_direct(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, SID_NONE);
|
||||||
|
set_option_direct(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, SID_NONE);
|
||||||
|
assert(ftbuf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already
|
||||||
|
ftbuf->b_p_swf = false;
|
||||||
|
ftbuf->b_p_ml = false;
|
||||||
|
ftbuf->b_p_ft = xstrdup(filetype);
|
||||||
|
|
||||||
TRY_WRAP(err, {
|
TRY_WRAP(err, {
|
||||||
set_option_value(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
|
|
||||||
set_option_value(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
|
|
||||||
set_option_value(kOptSwapfile, BOOLEAN_OPTVAL(false), OPT_LOCAL);
|
|
||||||
set_option_value(kOptModeline, BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline'
|
|
||||||
|
|
||||||
ftbuf->b_p_ft = xstrdup(filetype);
|
|
||||||
do_filetype_autocmd(ftbuf, false);
|
do_filetype_autocmd(ftbuf, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!bufref_valid(&bufref)) {
|
||||||
|
if (!ERROR_SET(err)) {
|
||||||
|
api_set_error(err, kErrorTypeException, "Internal buffer was deleted");
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return ftbuf;
|
return ftbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,13 +182,15 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
|||||||
}
|
}
|
||||||
|
|
||||||
aco_save_T aco;
|
aco_save_T aco;
|
||||||
|
bool aco_used;
|
||||||
|
|
||||||
buf_T *ftbuf = do_ft_buf(filetype, &aco, err);
|
buf_T *ftbuf = do_ft_buf(filetype, &aco, &aco_used, err);
|
||||||
if (ERROR_SET(err)) {
|
if (ERROR_SET(err)) {
|
||||||
if (ftbuf != NULL) {
|
if (aco_used) {
|
||||||
// restore curwin/curbuf and a few other things
|
// restore curwin/curbuf and a few other things
|
||||||
aucmd_restbuf(&aco);
|
aucmd_restbuf(&aco);
|
||||||
|
}
|
||||||
|
if (ftbuf != NULL) {
|
||||||
assert(curbuf != ftbuf); // safety check
|
assert(curbuf != ftbuf); // safety check
|
||||||
wipe_buffer(ftbuf, false);
|
wipe_buffer(ftbuf, false);
|
||||||
}
|
}
|
||||||
@@ -183,9 +206,10 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
|
|||||||
OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err);
|
OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err);
|
||||||
|
|
||||||
if (ftbuf != NULL) {
|
if (ftbuf != NULL) {
|
||||||
// restore curwin/curbuf and a few other things
|
if (aco_used) {
|
||||||
aucmd_restbuf(&aco);
|
// restore curwin/curbuf and a few other things
|
||||||
|
aucmd_restbuf(&aco);
|
||||||
|
}
|
||||||
assert(curbuf != ftbuf); // safety check
|
assert(curbuf != ftbuf); // safety check
|
||||||
wipe_buffer(ftbuf, false);
|
wipe_buffer(ftbuf, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1977,6 +1977,42 @@ describe('API', function()
|
|||||||
]])
|
]])
|
||||||
eq(false, api.nvim_get_option_value('modified', {}))
|
eq(false, api.nvim_get_option_value('modified', {}))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('errors if autocmds wipe the dummy buffer', function()
|
||||||
|
-- Wipe the dummy buffer. This will throw E813, but the buffer will still be wiped; check that
|
||||||
|
-- such errors from setting the filetype have priority.
|
||||||
|
command 'autocmd FileType * ++once bwipeout!'
|
||||||
|
eq(
|
||||||
|
'FileType Autocommands for "*": Vim(bwipeout):E813: Cannot close autocmd window',
|
||||||
|
pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' })
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Silence E813 to check that the error for wiping the dummy buffer is set.
|
||||||
|
command 'autocmd FileType * ++once silent! bwipeout!'
|
||||||
|
eq(
|
||||||
|
'Internal buffer was deleted',
|
||||||
|
pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' })
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('sets dummy buffer options without side-effects', function()
|
||||||
|
exec [[
|
||||||
|
let g:events = []
|
||||||
|
autocmd OptionSet * let g:events += [expand("<amatch>")]
|
||||||
|
autocmd FileType * ++once let g:bufhidden = &l:bufhidden
|
||||||
|
\| let g:buftype = &l:buftype
|
||||||
|
\| let g:swapfile = &l:swapfile
|
||||||
|
\| let g:modeline = &l:modeline
|
||||||
|
\| let g:bufloaded = bufloaded(bufnr())
|
||||||
|
]]
|
||||||
|
api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
|
||||||
|
eq({}, eval('g:events'))
|
||||||
|
eq('hide', eval('g:bufhidden'))
|
||||||
|
eq('nofile', eval('g:buftype'))
|
||||||
|
eq(0, eval('g:swapfile'))
|
||||||
|
eq(0, eval('g:modeline'))
|
||||||
|
eq(1, eval('g:bufloaded'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
|
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
|
||||||
|
|||||||
Reference in New Issue
Block a user