Skip to content

Commit 6ce7b9b

Browse files
seandewargithub-actions[bot]
authored andcommitted
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. (cherry picked from commit 3cb462a)
1 parent b4cd55d commit 6ce7b9b

File tree

2 files changed

+73
-13
lines changed

2 files changed

+73
-13
lines changed

src/nvim/api/options.c

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "nvim/buffer.h"
1414
#include "nvim/buffer_defs.h"
1515
#include "nvim/globals.h"
16+
#include "nvim/memline.h"
1617
#include "nvim/memory.h"
1718
#include "nvim/memory_defs.h"
1819
#include "nvim/option.h"
@@ -101,8 +102,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *
101102
}
102103

103104
/// Create a dummy buffer and run the FileType autocmd on it.
104-
static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
105+
static buf_T *do_ft_buf(const char *filetype, aco_save_T *aco, bool *aco_used, Error *err)
106+
FUNC_ATTR_NONNULL_ARG(2, 3, 4)
105107
{
108+
*aco_used = false;
106109
if (filetype == NULL) {
107110
return NULL;
108111
}
@@ -114,19 +117,37 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
114117
return NULL;
115118
}
116119

120+
// Open a memline for use by autocommands.
121+
if (ml_open(ftbuf) == FAIL) {
122+
api_set_error(err, kErrorTypeException, "Could not load internal buffer");
123+
return ftbuf;
124+
}
125+
126+
bufref_T bufref;
127+
set_bufref(&bufref, ftbuf);
128+
117129
// Set curwin/curbuf to buf and save a few things.
118130
aucmd_prepbuf(aco, ftbuf);
131+
*aco_used = true;
119132

120-
TRY_WRAP(err, {
121-
set_option_value(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
122-
set_option_value(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
123-
set_option_value(kOptSwapfile, BOOLEAN_OPTVAL(false), OPT_LOCAL);
124-
set_option_value(kOptModeline, BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline'
133+
set_option_direct(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, SID_NONE);
134+
set_option_direct(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, SID_NONE);
135+
assert(ftbuf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already
136+
ftbuf->b_p_swf = false;
137+
ftbuf->b_p_ml = false;
138+
ftbuf->b_p_ft = xstrdup(filetype);
125139

126-
ftbuf->b_p_ft = xstrdup(filetype);
140+
TRY_WRAP(err, {
127141
do_filetype_autocmd(ftbuf, false);
128142
});
129143

144+
if (!bufref_valid(&bufref)) {
145+
if (!ERROR_SET(err)) {
146+
api_set_error(err, kErrorTypeException, "Internal buffer was deleted");
147+
}
148+
return NULL;
149+
}
150+
130151
return ftbuf;
131152
}
132153

@@ -163,13 +184,15 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
163184
}
164185

165186
aco_save_T aco;
187+
bool aco_used;
166188

167-
buf_T *ftbuf = do_ft_buf(filetype, &aco, err);
189+
buf_T *ftbuf = do_ft_buf(filetype, &aco, &aco_used, err);
168190
if (ERROR_SET(err)) {
169-
if (ftbuf != NULL) {
191+
if (aco_used) {
170192
// restore curwin/curbuf and a few other things
171193
aucmd_restbuf(&aco);
172-
194+
}
195+
if (ftbuf != NULL) {
173196
assert(curbuf != ftbuf); // safety check
174197
wipe_buffer(ftbuf, false);
175198
}
@@ -185,9 +208,10 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
185208
OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err);
186209

187210
if (ftbuf != NULL) {
188-
// restore curwin/curbuf and a few other things
189-
aucmd_restbuf(&aco);
190-
211+
if (aco_used) {
212+
// restore curwin/curbuf and a few other things
213+
aucmd_restbuf(&aco);
214+
}
191215
assert(curbuf != ftbuf); // safety check
192216
wipe_buffer(ftbuf, false);
193217
}

test/functional/api/vim_spec.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,42 @@ describe('API', function()
19901990
]])
19911991
eq(false, api.nvim_get_option_value('modified', {}))
19921992
end)
1993+
1994+
it('errors if autocmds wipe the dummy buffer', function()
1995+
-- Wipe the dummy buffer. This will throw E813, but the buffer will still be wiped; check that
1996+
-- such errors from setting the filetype have priority.
1997+
command 'autocmd FileType * ++once bwipeout!'
1998+
eq(
1999+
'FileType Autocommands for "*": Vim(bwipeout):E813: Cannot close autocmd window',
2000+
pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' })
2001+
)
2002+
2003+
-- Silence E813 to check that the error for wiping the dummy buffer is set.
2004+
command 'autocmd FileType * ++once silent! bwipeout!'
2005+
eq(
2006+
'Internal buffer was deleted',
2007+
pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' })
2008+
)
2009+
end)
2010+
2011+
it('sets dummy buffer options without side-effects', function()
2012+
exec [[
2013+
let g:events = []
2014+
autocmd OptionSet * let g:events += [expand("<amatch>")]
2015+
autocmd FileType * ++once let g:bufhidden = &l:bufhidden
2016+
\| let g:buftype = &l:buftype
2017+
\| let g:swapfile = &l:swapfile
2018+
\| let g:modeline = &l:modeline
2019+
\| let g:bufloaded = bufloaded(bufnr())
2020+
]]
2021+
api.nvim_get_option_value('formatexpr', { filetype = 'lua' })
2022+
eq({}, eval('g:events'))
2023+
eq('hide', eval('g:bufhidden'))
2024+
eq('nofile', eval('g:buftype'))
2025+
eq(0, eval('g:swapfile'))
2026+
eq(0, eval('g:modeline'))
2027+
eq(1, eval('g:bufloaded'))
2028+
end)
19932029
end)
19942030

19952031
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()

0 commit comments

Comments
 (0)