fix(api): on_detach consistently before buf_freeall autocmds

Problem: on_detach may be called after buf_freeall and other important things,
plus its textlock restrictions are insufficient. This can cause issues such as
leaks, internal errors and crashes.

Solution: disable buffer updates in buf_freeall, before autocommands (like the
order after #35355 and when do_ecmd reloads a buffer). Don't do so in
free_buffer_stuff; it's not safe to run user code there, and buf_freeall already
runs before then; just free them to avoid leaks if buf_freeall autocommands
registered more for some reason.

Fixes #28084
Fixes #33967
Fixes #35116
This commit is contained in:
Sean Dewar
2025-08-17 03:06:44 +01:00
parent d8ed43c6a7
commit 2211953266
3 changed files with 139 additions and 42 deletions

View File

@@ -656,10 +656,6 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
buf->b_nwindows = nwindows;
// Disable buffer-updates for the current buffer.
// No need to check `unload_buf`: in that case the function returned above.
buf_updates_unload(buf, false);
buf_freeall(buf, ((del_buf ? BFA_DEL : 0)
+ (wipe_buf ? BFA_WIPE : 0)
+ (ignore_abort ? BFA_IGNORE_ABORT : 0)));
@@ -788,6 +784,11 @@ void buf_freeall(buf_T *buf, int flags)
bufref_T bufref;
set_bufref(&bufref, buf);
buf_updates_unload(buf, false);
if (!bufref_valid(&bufref)) {
// on_detach callback deleted the buffer.
return;
}
if ((buf->b_ml.ml_mfp != NULL)
&& apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, false, buf)
&& !bufref_valid(&bufref)) {
@@ -935,7 +936,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
XFREE_CLEAR(buf->b_start_fenc);
buf_updates_unload(buf, false);
buf_free_callbacks(buf);
}
/// Go to another buffer. Handles the result of the ATTENTION dialog.

View File

@@ -2720,13 +2720,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
goto theend;
}
u_unchanged(curbuf);
buf_updates_unload(curbuf, false);
buf_freeall(curbuf, BFA_KEEP_UNDO);
// Tell readfile() not to clear or reload undo info.
readfile_flags = READ_KEEP_UNDO;
} else {
buf_updates_unload(curbuf, false);
buf_freeall(curbuf, 0); // Free all things for buffer.
}
// If autocommands deleted the buffer we were going to re-edit, give