diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index d2e2ff5032..62c72ace3e 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -1071,6 +1071,15 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en return res; } + // Emitting a msg below can run user code (ui2 `msg_show` handler), which may change CWD and thus + // reallocate the buffer names. #41417 + char *const fname_copy = xstrdup(fname); + char *const sfname_copy = xstrdup(sfname); + char *const ffname_copy = xstrdup(ffname); + fname = fname_copy; + sfname = sfname_copy; + ffname = ffname_copy; + if (cmdmod.cmod_flags & CMOD_LOCKMARKS) { // restore the original '[ and '] positions buf->b_op_start = orig_start; @@ -1858,5 +1867,9 @@ nofail: got_int |= prev_got_int; + xfree(fname_copy); + xfree(sfname_copy); + xfree(ffname_copy); + return retval; } diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua index e8b60e4978..98f07521b7 100644 --- a/test/functional/ex_cmds/write_spec.lua +++ b/test/functional/ex_cmds/write_spec.lua @@ -264,6 +264,25 @@ describe(':write', function() ) end) + it('unaffected if a Progress handler changes CWD #41417', function() + -- ASAN catches the read of the freed name. Also assert the written file name/contents. + local dir = vim.fs.normalize(t.tmpname(false)) + t.mkdir(dir) + local file = dir .. '/f.txt' + write_file(file, 'one\n') + command('edit ' .. file) + command('lcd ' .. dir) + eq('f.txt', fn.bufname('%')) + -- Frees every buffer's short name, twice; the net CWD is unchanged. + command(('autocmd Progress * lcd %s | lcd %s'):format(vim.fs.dirname(dir), dir)) + api.nvim_buf_set_lines(0, 0, -1, true, { 'one', 'two' }) + command('write') + + eq('f.txt', fn.bufname('%')) + eq({ 'one', 'two' }, fn.readfile(file)) + eq({ 'f.txt' }, fn.readdir(dir)) + end) + it('handles a multi-byte sequence crossing the buffer boundary converting with iconv', function() local content = string.rep('a', 1024 * 8 - 1) .. 'Дbbbbb' api.nvim_buf_set_lines(0, 0, 1, true, { content })