From b7a0c71a545cf20bf1d142f08bc6568b02015c57 Mon Sep 17 00:00:00 2001 From: Samiul Islam Date: Fri, 4 Sep 2026 23:06:50 +0600 Subject: [PATCH] fix(multicursor): prevent clipboard crash after cursor jump #41698 Problem: Changing from an empty-line cursor after a multicursor jump can crash while flushing a deferred clipboard update. Exact context restore can leave an omitted register with a null array and stale non-zero size. Solution: Make free_register() fully reset the register after freeing its contents, so it always leaves a valid empty register. Add a regression test. Signed-off-by: sami --- src/nvim/register.c | 13 ++++++------- test/functional/editor/mcursor_spec.lua | 26 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/nvim/register.c b/src/nvim/register.c index 5ccf5ab1e0..fcbd16be90 100644 --- a/src/nvim/register.c +++ b/src/nvim/register.c @@ -986,14 +986,13 @@ void free_register(yankreg_T *reg) FUNC_ATTR_NONNULL_ALL { XFREE_CLEAR(reg->additional_data); - if (reg->y_array == NULL) { - return; + if (reg->y_array != NULL) { + for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included + API_CLEAR_STRING(reg->y_array[i]); + } + XFREE_CLEAR(reg->y_array); } - - for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included - API_CLEAR_STRING(reg->y_array[i]); - } - XFREE_CLEAR(reg->y_array); + *reg = (yankreg_T){ 0 }; } /// Copy a block range into a register. diff --git a/test/functional/editor/mcursor_spec.lua b/test/functional/editor/mcursor_spec.lua index a97117fd2a..458a4af632 100644 --- a/test/functional/editor/mcursor_spec.lua +++ b/test/functional/editor/mcursor_spec.lua @@ -2628,6 +2628,32 @@ describe('multicursor', function() end) describe('clipboard', function() + it("does not crash after jumping to an empty line with 'clipboard'", function() + n.exec_lua([[ + _G.content = {} + vim.g.clipboard = { + name = 'test', + copy = { + ['+'] = function(lines) + _G.content = lines + end, + }, + paste = { + ['+'] = function() + return _G.content + end, + }, + } + vim.o.clipboard = 'unnamedplus' + ]]) + cursors({ '', 'aa' }, 'Qj') -- Cursor on the empty line, primary on the non-empty line. + feed(']C') -- Make the empty-line cursor primary. + feed('C') + n.assert_alive() + feed('') + eq({ '', '' }, get_lines()) + end) + it("perf: provider syncs once per cascade with 'clipboard'", function() n.exec_lua([[ _G.copies = 0