commit 0f64f0f5b66b19a6d118d88a54a2a2d069b602c6
parent 2e70f3522b5a4ab35341d2e6ce3f04a5bddef436
Author: Cameron Ring <cameron@cs.stanford.edu>
Date: Wed, 10 Sep 2025 21:37:42 -0700
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.
Diffstat:
2 files changed, 52 insertions(+), 18 deletions(-)
diff --git 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
@@ -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')