diff options
author | zeertzjq <zeertzjq@outlook.com> | 2022-12-02 20:39:24 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-02 20:39:24 +0800 |
commit | 1145a9b2485a4e5072cffe28a958da983cd59e84 (patch) | |
tree | 13059f09a9825a7eae3d75c8556436845dc1e492 | |
parent | 805c83f43e3d6832a54bc4a1de1dd4d4da43d58e (diff) | |
download | rneovim-1145a9b2485a4e5072cffe28a958da983cd59e84.tar.gz rneovim-1145a9b2485a4e5072cffe28a958da983cd59e84.tar.bz2 rneovim-1145a9b2485a4e5072cffe28a958da983cd59e84.zip |
feat(aucmd_win): allow crazy things with hidden buffers (#21250)
Problem: Crash when doing crazy things with hidden buffers.
Solution: Dynamically allocate the list of autocommand windows.
-rw-r--r-- | src/nvim/autocmd.c | 37 | ||||
-rw-r--r-- | src/nvim/buffer.c | 34 | ||||
-rw-r--r-- | src/nvim/diff.c | 5 | ||||
-rw-r--r-- | src/nvim/eval/buffer.c | 6 | ||||
-rw-r--r-- | src/nvim/eval/vars.c | 10 | ||||
-rw-r--r-- | src/nvim/ex_cmds2.c | 6 | ||||
-rw-r--r-- | src/nvim/fileio.c | 43 | ||||
-rw-r--r-- | src/nvim/globals.h | 13 | ||||
-rw-r--r-- | src/nvim/main.c | 2 | ||||
-rw-r--r-- | src/nvim/quickfix.c | 95 | ||||
-rw-r--r-- | src/nvim/window.c | 2 | ||||
-rw-r--r-- | test/functional/lua/vim_spec.lua | 40 |
12 files changed, 137 insertions, 156 deletions
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 57d829d186..ab6c22ff6c 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -633,11 +633,6 @@ void do_augroup(char *arg, int del_group) } } -void autocmd_init(void) -{ - CLEAR_FIELD(aucmd_win); -} - #if defined(EXITFREE) void free_all_autocmds(void) { @@ -1328,13 +1323,6 @@ void ex_doautoall(exarg_T *eap) // Find a window for this buffer and save some values. aucmd_prepbuf(&aco, buf); - if (curbuf != buf) { - // Failed to find a window for this buffer. Better not execute - // autocommands then. - retval = FAIL; - break; - } - set_bufref(&bufref, buf); // execute the autocommands for this buffer @@ -1385,7 +1373,6 @@ bool check_nomodeline(char **argp) /// If the current buffer is not in any visible window, put it in a temporary /// floating window using an entry in `aucmd_win[]`. /// Set `curbuf` and `curwin` to match `buf`. -/// When this fails `curbuf` is not equal `buf`. /// /// @param aco structure to save values in /// @param buf new curbuf @@ -1413,23 +1400,23 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) if (win == NULL) { for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) { if (!aucmd_win[auc_idx].auc_win_used) { - if (aucmd_win[auc_idx].auc_win == NULL) { - win_alloc_aucmd_win(auc_idx); - need_append = false; - } - auc_win = aucmd_win[auc_idx].auc_win; - aucmd_win[auc_idx].auc_win_used = true; break; } } - // If this fails (using all AUCMD_WIN_COUNT entries) - // then we can't reliably execute the autocmd, - // return with "curbuf" unequal "buf". - if (auc_win == NULL) { - assert(curbuf != buf); - return; + if (auc_idx == AUCMD_WIN_COUNT) { + kv_push(aucmd_win_vec, ((aucmdwin_T){ + .auc_win = NULL, + .auc_win_used = false, + })); + } + + if (aucmd_win[auc_idx].auc_win == NULL) { + win_alloc_aucmd_win(auc_idx); + need_append = false; } + auc_win = aucmd_win[auc_idx].auc_win; + aucmd_win[auc_idx].auc_win_used = true; } aco->save_curwin_handle = curwin->handle; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 2060f799bd..c9fe5f9670 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -181,13 +181,11 @@ void buffer_ensure_loaded(buf_T *buf) if (buf->b_ml.ml_mfp == NULL) { aco_save_T aco; - // Make sure the buffer is in a window. If not then skip it. + // Make sure the buffer is in a window. aucmd_prepbuf(&aco, buf); - if (curbuf == buf) { - swap_exists_action = SEA_NONE; - open_buffer(false, NULL, 0); - aucmd_restbuf(&aco); - } + swap_exists_action = SEA_NONE; + open_buffer(false, NULL, 0); + aucmd_restbuf(&aco); } } @@ -369,20 +367,17 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) aco_save_T aco; // Go to the buffer that was opened, make sure it is in a window. - // If not then skip it. aucmd_prepbuf(&aco, old_curbuf.br_buf); - if (curbuf == old_curbuf.br_buf) { - do_modelines(0); - curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); - - if ((flags & READ_NOWINENTER) == 0) { - apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf, - &retval); - } + do_modelines(0); + curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); - // restore curwin/curbuf and a few other things - aucmd_restbuf(&aco); + if ((flags & READ_NOWINENTER) == 0) { + apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf, + &retval); } + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); } return retval; @@ -4177,11 +4172,6 @@ bool buf_contents_changed(buf_T *buf) // Set curwin/curbuf to buf and save a few things. aco_save_T aco; aucmd_prepbuf(&aco, newbuf); - if (curbuf != newbuf) { - // Failed to find a window for "newbuf". - wipe_buffer(newbuf, false); - return true; - } if (ml_open(curbuf) == OK && readfile(buf->b_ffname, buf->b_fname, diff --git a/src/nvim/diff.c b/src/nvim/diff.c index ac637c2c70..011632a4ef 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2944,11 +2944,6 @@ void ex_diffgetput(exarg_T *eap) // Set curwin/curbuf to buf and save a few things. aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]); - if (curbuf != curtab->tp_diffbuf[idx_other]) { - // Could not find a window for this buffer, the rest is likely to - // fail. - goto theend; - } } const int idx_from = eap->cmdidx == CMD_diffget ? idx_other : idx_cur; diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index 6ebf54ef0f..72eb0f8d67 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -86,8 +86,6 @@ static void find_win_for_curbuf(void) /// /// Information is saved in "cob" and MUST be restored by calling /// change_other_buffer_restore(). -/// -/// If this fails then "curbuf" will not be equal to "buf". static void change_other_buffer_prepare(cob_T *cob, buf_T *buf) { CLEAR_POINTER(cob); @@ -105,9 +103,7 @@ static void change_other_buffer_prepare(cob_T *cob, buf_T *buf) // curwin->w_buffer differ from "curbuf", use the autocmd window. curbuf = curwin->w_buffer; aucmd_prepbuf(&cob->cob_aco, buf); - if (curbuf == buf) { - cob->cob_using_aco = true; - } + cob->cob_using_aco = true; } } diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index f002071ad2..d37631af8c 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1802,13 +1802,11 @@ void f_setbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Set curbuf to be our buf, temporarily. aucmd_prepbuf(&aco, buf); - if (curbuf == buf) { - // Only when it worked to set "curbuf". - set_option_from_tv(varname + 1, varp); - // reset notion of buffer - aucmd_restbuf(&aco); - } + set_option_from_tv(varname + 1, varp); + + // reset notion of buffer + aucmd_restbuf(&aco); } else { const size_t varname_len = strlen(varname); char *const bufvarname = xmalloc(varname_len + 3); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index d3c0c417e1..b6489e97b2 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -683,10 +683,8 @@ void ex_listdo(exarg_T *eap) curbuf); } else { aucmd_prepbuf(&aco, buf); - if (curbuf == buf) { - apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, true, buf); - aucmd_restbuf(&aco); - } + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, true, buf); + aucmd_restbuf(&aco); } // start over, in case autocommands messed things up. diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index dabec5e95e..eaaed8b25c 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2310,12 +2310,6 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en // Set curwin/curbuf to buf and save a few things. aucmd_prepbuf(&aco, buf); - if (curbuf != buf) { - // Could not find a window for "buf". Doing more might cause - // problems, better bail out. - return FAIL; - } - set_bufref(&bufref, buf); if (append) { @@ -3618,27 +3612,25 @@ nofail: // Apply POST autocommands. // Careful: The autocommands may call buf_write() recursively! - // Only do this when a window was found for "buf". aucmd_prepbuf(&aco, buf); - if (curbuf == buf) { - if (append) { - apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, - false, curbuf, eap); - } else if (filtering) { - apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, - false, curbuf, eap); - } else if (reset_changed && whole) { - apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, - false, curbuf, eap); - } else { - apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, - false, curbuf, eap); - } - // restore curwin/curbuf and a few other things - aucmd_restbuf(&aco); + if (append) { + apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, + false, curbuf, eap); + } else if (filtering) { + apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, + false, curbuf, eap); + } else if (reset_changed && whole) { + apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, + false, curbuf, eap); + } else { + apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, + false, curbuf, eap); } + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + if (aborting()) { // autocmds may abort script processing retval = false; } @@ -5013,11 +5005,6 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) // Set curwin/curbuf for "buf" and save some things. aucmd_prepbuf(&aco, buf); - if (curbuf != buf) { - // Failed to find a window for "buf", it is dangerous to continue, - // better bail out. - return; - } // Unless reload_options is set, we only want to read the text from the // file, not reset the syntax highlighting, clear marks, diff status, etc. diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 9fcde4f17a..8f9fa6673c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -422,19 +422,18 @@ EXTERN win_T *prevwin INIT(= NULL); // previous window EXTERN win_T *curwin; // currently active window -/// When executing autocommands for a buffer that is not in any window, a -/// special window is created to handle the side effects. When autocommands -/// nest we may need more than one. Allow for up to five, if more are needed -/// something crazy is happening. -enum { AUCMD_WIN_COUNT = 5, }; - typedef struct { win_T *auc_win; ///< Window used in aucmd_prepbuf(). When not NULL the ///< window has been allocated. bool auc_win_used; ///< This auc_win is being used. } aucmdwin_T; -EXTERN aucmdwin_T aucmd_win[AUCMD_WIN_COUNT]; +/// When executing autocommands for a buffer that is not in any window, a +/// special window is created to handle the side effects. When autocommands +/// nest we may need more than one. +EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT(= KV_INITIAL_VALUE); +#define aucmd_win (aucmd_win_vec.items) +#define AUCMD_WIN_COUNT ((int)aucmd_win_vec.size) // The window layout is kept in a tree of frames. topframe points to the top // of the tree. diff --git a/src/nvim/main.c b/src/nvim/main.c index 88ce8fb2e8..78b59887e7 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -251,8 +251,6 @@ int main(int argc, char **argv) // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); - autocmd_init(); - // Since os_open is called during the init_startuptime, we need to call // fs_init before it. fs_init(); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index be6ddede2e..deac0bb8a1 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3902,27 +3902,21 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) aco_save_T aco; - bool do_fill = true; if (old_last == NULL) { // set curwin/curbuf to buf and save a few things aucmd_prepbuf(&aco, buf); - if (curbuf != buf) { - do_fill = false; // failed to find a window for "buf" - } } - if (do_fill) { - qf_update_win_titlevar(qi); + qf_update_win_titlevar(qi); - qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid); - buf_inc_changedtick(buf); + qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid); + buf_inc_changedtick(buf); - if (old_last == NULL) { - (void)qf_win_pos_update(qi, 0); + if (old_last == NULL) { + (void)qf_win_pos_update(qi, 0); - // restore curwin/curbuf and a few other things - aucmd_restbuf(&aco); - } + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); } // Only redraw when added lines are visible. This avoids flickering when @@ -5469,11 +5463,9 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo // options! aco_save_T aco; aucmd_prepbuf(&aco, buf); - if (curbuf == buf) { - apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf); - do_modelines(OPT_NOWIN); - aucmd_restbuf(&aco); - } + apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf); + do_modelines(OPT_NOWIN); + aucmd_restbuf(&aco); } } } @@ -5631,43 +5623,42 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin // set curwin/curbuf to buf and save a few things aco_save_T aco; aucmd_prepbuf(&aco, newbuf); - if (curbuf == newbuf) { - // Need to set the filename for autocommands. - (void)setfname(curbuf, fname, NULL, false); - - // Create swap file now to avoid the ATTENTION message. - check_need_swap(true); - - // Remove the "dummy" flag, otherwise autocommands may not - // work. - curbuf->b_flags &= ~BF_DUMMY; - - bufref_T newbuf_to_wipe; - newbuf_to_wipe.br_buf = NULL; - int readfile_result = readfile(fname, NULL, (linenr_T)0, (linenr_T)0, - (linenr_T)MAXLNUM, NULL, - READ_NEW | READ_DUMMY, false); - newbuf->b_locked--; - if (readfile_result == OK - && !got_int - && !(curbuf->b_flags & BF_NEW)) { - failed = false; - if (curbuf != newbuf) { - // Bloody autocommands changed the buffer! Can happen when - // using netrw and editing a remote file. Use the current - // buffer instead, delete the dummy one after restoring the - // window stuff. - set_bufref(&newbuf_to_wipe, newbuf); - newbuf = curbuf; - } + + // Need to set the filename for autocommands. + (void)setfname(curbuf, fname, NULL, false); + + // Create swap file now to avoid the ATTENTION message. + check_need_swap(true); + + // Remove the "dummy" flag, otherwise autocommands may not + // work. + curbuf->b_flags &= ~BF_DUMMY; + + bufref_T newbuf_to_wipe; + newbuf_to_wipe.br_buf = NULL; + int readfile_result = readfile(fname, NULL, (linenr_T)0, (linenr_T)0, + (linenr_T)MAXLNUM, NULL, + READ_NEW | READ_DUMMY, false); + newbuf->b_locked--; + if (readfile_result == OK + && !got_int + && !(curbuf->b_flags & BF_NEW)) { + failed = false; + if (curbuf != newbuf) { + // Bloody autocommands changed the buffer! Can happen when + // using netrw and editing a remote file. Use the current + // buffer instead, delete the dummy one after restoring the + // window stuff. + set_bufref(&newbuf_to_wipe, newbuf); + newbuf = curbuf; } + } - // Restore curwin/curbuf and a few other things. - aucmd_restbuf(&aco); + // Restore curwin/curbuf and a few other things. + aucmd_restbuf(&aco); - if (newbuf_to_wipe.br_buf != NULL && bufref_valid(&newbuf_to_wipe)) { - wipe_buffer(newbuf_to_wipe.br_buf, false); - } + if (newbuf_to_wipe.br_buf != NULL && bufref_valid(&newbuf_to_wipe)) { + wipe_buffer(newbuf_to_wipe.br_buf, false); } // Add back the "dummy" flag, otherwise buflist_findname_file_id() diff --git a/src/nvim/window.c b/src/nvim/window.c index 79a90ab8af..1f80f14f26 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3122,6 +3122,8 @@ void win_free_all(void) } } + kv_destroy(aucmd_win_vec); + while (firstwin != NULL) { int dummy; (void)win_free_mem(firstwin, &dummy, NULL); diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 4266d30e73..c74e88f55d 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -2679,6 +2679,46 @@ describe('lua stdlib', function() a.nvim_buf_call(a.nvim_create_buf(false, true), function() vim.cmd "redraw" end) ]] end) + + it('can be nested crazily with hidden buffers', function() + eq(true, exec_lua([[ + local function scratch_buf_call(fn) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(buf, 'cindent', true) + return vim.api.nvim_buf_call(buf, function() + return vim.api.nvim_get_current_buf() == buf + and vim.api.nvim_buf_get_option(buf, 'cindent') + and fn() + end) and vim.api.nvim_buf_delete(buf, {}) == nil + end + + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return true + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + ]])) + end) end) describe('vim.api.nvim_win_call', function() |