fix(terminal): skip setting string_initial to false on no-op (#34176)

Problem:

Currently undefined behavior can occur when `string_fragment()` is
called with `OSC_COMMAND`. This is because when the state changes to
`OSC_COMMAND`, `string_initial` is set to true. Then in some cases,
directly after this `string_initial` will be set back to false before
the on_osc callback is called, this leads to `term_settermprop()` never
initializing the title.

Solution:

In all of the no-op cases in `string_fragment()` currently, we continue
to the end of the function where `vt->parser.string_initial` is set to
false. This change returns in the no-op cases instead since in these
cases the string has not yet been terminated and sent to the callback.

Note:

This change also adds a test with a byte sequence from the file
in #34028 that caused nvim to crash. This byte sequences is the shortest
sequence I could trim down from that file that still would trigger the
crash. There are also two other tests I added which validate that
setting the title with OSC-0 and OSC-2 still works.

Fixes: #34028
This commit is contained in:
Gabriel Ford
2025-05-29 14:29:16 -04:00
committed by GitHub
parent f82219c490
commit b28bbee539
2 changed files with 49 additions and 1 deletions

View File

@ -122,7 +122,7 @@ static void string_fragment(VTerm *vt, const char *str, size_t len, bool final)
case CSI_INTERMED:
case OSC_COMMAND:
case DCS_COMMAND:
break;
return;
}
vt->parser.string_initial = false;

View File

@ -4,6 +4,10 @@ local clear = n.clear
local api = n.api
local assert_alive = n.assert_alive
local OSC_PREFIX = string.char(0x1b, 0x5d)
local BEL = string.char(0x07)
local NUL = string.char(0x00)
describe(':terminal', function()
before_each(clear)
@ -12,4 +16,48 @@ describe(':terminal', function()
api.nvim_chan_send(chan, '\027]8;;https://example.com\027\\Example\027]8;;\027\n')
assert_alive()
end)
it('handles OSC-2 title setting', function()
-- OSC-2 should set title.
local chan = api.nvim_open_term(0, {})
local input = OSC_PREFIX .. '2;This title set with OSC 2' .. BEL
api.nvim_chan_send(chan, input)
--- @type string
local term_title = api.nvim_buf_get_var(0, 'term_title')
assert.Equal(term_title, 'This title set with OSC 2')
assert_alive()
end)
it('handles OSC-0 title and icon setting', function()
-- OSC-0 should set title and icon name to the same string. We currently ignore the icon name,
-- but the title should still be reflected.
local chan = api.nvim_open_term(0, {})
local input = OSC_PREFIX .. '0;This title set with OSC 0' .. BEL
api.nvim_chan_send(chan, input)
--- @type string
local term_title = api.nvim_buf_get_var(0, 'term_title')
assert.Equal(term_title, 'This title set with OSC 0')
assert_alive()
end)
it('handles control character following OSC prefix #34028', function()
local chan = api.nvim_open_term(0, {})
-- In order to test for the crash found in #34028 we need a ctrl char following the OSC_PREFIX
-- which causes `string_fragment()` to be called while in OSC_COMMAND mode, this caused
-- initial_string to be flipped back to false. At the end we need two more non-BEL control
-- characters, one to write into the 1 byte buffer, then another to trigger the callback one
-- more time so that realloc notices that it's internal data has been overwritten.
local input = OSC_PREFIX .. NUL .. '0;aaaaaaaaaaaaaaaaaaaaaaaaaaa' .. NUL .. NUL
api.nvim_chan_send(chan, input)
assert_alive()
-- On some platforms such as MacOS we need a longer string to reproduce the crash from #34028.
input = OSC_PREFIX .. NUL .. '0;'
for _ = 1, 256 do
input = input .. 'a'
end
input = input .. NUL .. NUL
api.nvim_chan_send(chan, input)
assert_alive()
end)
end)