From 0f64f0f5b66b19a6d118d88a54a2a2d069b602c6 Mon Sep 17 00:00:00 2001 From: Cameron Ring Date: Wed, 10 Sep 2025 21:37:42 -0700 Subject: [PATCH] fix(startup): crash in read_stdin #35699 Problem: Crash on startup in some situations due to call to `set_curbuf` with the current value of `cur_buf`. Solution: Switch buffers before doing the wipeout of the stdin buffer. Use cmdline cmds instead of raw buffer pointers to avoid lifetime issues. --- src/nvim/main.c | 44 ++++++++++++++++----------- test/functional/core/startup_spec.lua | 26 +++++++++++++++- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/nvim/main.c b/src/nvim/main.c index cfc11359fe..8270ddd63c 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1625,39 +1625,49 @@ static void read_stdin(void) swap_exists_action = SEA_DIALOG; no_wait_return = true; bool save_msg_didany = msg_didany; - buf_T *prev_buf = NULL; if (curbuf->b_ffname) { // curbuf is already opened for a file, create a new buffer for stdin. #35269 - buf_T *newbuf = buflist_new(NULL, NULL, 0, BLN_LISTED); - if (newbuf == NULL) { + buf_T *stdin_buf = buflist_new(NULL, NULL, 0, BLN_LISTED); + if (stdin_buf == NULL) { semsg("Failed to create buffer for stdin"); return; } - // remember the current buffer so we can go back to it - prev_buf = curbuf; - set_curbuf(newbuf, 0, false); + // remember the current buffer number so we can go back to it + handle_T initial_buf_handle = curbuf->handle; + + // set the buffer we just created as curbuf so we can read stdin into it + set_curbuf(stdin_buf, 0, false); readfile(NULL, NULL, 0, 0, (linenr_T)MAXLNUM, NULL, READ_NEW + READ_STDIN, true); + + // remember stdin_buf_handle so we can close it if stdin_buf ends up empty + handle_T stdin_buf_handle = stdin_buf->handle; + bool stdin_buf_empty = buf_is_empty(curbuf); + + // switch back to the original starting buffer + char buf[100]; + vim_snprintf(buf, sizeof(buf), "silent! buffer %d", initial_buf_handle); + do_cmdline_cmd(buf); + + if (stdin_buf_empty) { + // stdin buffer may be first or last ("echo foo | nvim file1 -"). #35269 + // only wipe buffer after having switched to original starting buffer. #35681 + vim_snprintf(buf, sizeof(buf), "silent! bwipeout! %d", stdin_buf_handle); + do_cmdline_cmd(buf); + } } else { + // stdin buffer is first so we can just use curbuf set_buflisted(true); // Create memfile and read from stdin. open_buffer(true, NULL, 0); - } - - if (buf_is_empty(curbuf)) { // stdin was empty so we should wipe it (e.g. "echo file1 | xargs nvim"). #8561 - // stdin buffer may be first or last ("echo foo | nvim file1 -"). #35269 - if ((curbuf->b_next != NULL) || (curbuf->b_prev != NULL)) { - do_bufdel(DOBUF_WIPE, NULL, 0, 0, 0, 1); + if (buf_is_empty(curbuf) && curbuf->b_next != NULL) { + do_cmdline_cmd("silent! bnext"); + do_cmdline_cmd("silent! bwipeout 1"); } } - // we had to switch buffers to load stdin, switch back - if (prev_buf) { - set_curbuf(prev_buf, 0, false); - } - no_wait_return = false; msg_didany = save_msg_didany; TIME_MSG("reading stdin"); diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 96a28d337c..9095cd157d 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -536,7 +536,7 @@ describe('startup', function() it('if stdin is empty and - is last: selects buffer 1, deletes buffer 3 #35269', function() eq( - '\r\n 1 %a "file1" line 0\r\n 2 #h "file2" line 1', + '\r\n 1 %a "file1" line 0\r\n 2 "file2" line 0', fn.system({ nvim_prog, '-n', @@ -554,6 +554,30 @@ describe('startup', function() ) end) + it("empty stdin with terminal split doesn't crash #35681", function() + eq( + 'nocrash', + fn.system({ + nvim_prog, + '-n', + '-u', + 'NONE', + '-i', + 'NONE', + '--headless', + '--cmd', + 'term', + '+split', + '+quit!', + '+bw!', + '+bw!', + '+echo "nocrash"', + "+call timer_start(1, { -> execute('qa') })", -- need to let event handling happen + '-', + }, { '' }) + ) + end) + it('stdin with -es/-Es #7679', function() local input = { 'append', 'line1', 'line2', '.', '%print', '' } local inputstr = table.concat(input, '\n')