mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
fix(decoration): fix crash when on_lines decor provider modifies marktree
If a "on_lines" callback changes the structure of the marktree, the iterator (which is used for an entire window viewport) might now point to invalid memory. Restore the iterator to the beginning of the line in this case. fixes #29484
This commit is contained in:
parent
fa298fd2f4
commit
33ff546b50
@ -303,12 +303,24 @@ static void decor_free_inner(DecorVirtText *vt, uint32_t first_idx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if we are in a callback while drawing, which might invalidate the marktree iterator.
|
||||
///
|
||||
/// This should be called whenever a structural modification has been done to a
|
||||
/// marktree in a public API function (i e any change which adds or deletes marks).
|
||||
void decor_state_invalidate(buf_T *buf)
|
||||
{
|
||||
if (decor_state.win && decor_state.win->w_buffer == buf) {
|
||||
decor_state.itr_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void decor_check_to_be_deleted(void)
|
||||
{
|
||||
assert(!decor_state.running_decor_provider);
|
||||
decor_free_inner(to_free_virt, to_free_sh);
|
||||
to_free_virt = NULL;
|
||||
to_free_sh = DECOR_ID_INVALID;
|
||||
decor_state.win = NULL;
|
||||
}
|
||||
|
||||
void decor_state_free(DecorState *state)
|
||||
@ -447,6 +459,8 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state)
|
||||
{
|
||||
buf_T *buf = wp->w_buffer;
|
||||
state->top_row = top_row;
|
||||
state->itr_valid = true;
|
||||
|
||||
if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) {
|
||||
return false;
|
||||
}
|
||||
@ -489,7 +503,11 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state)
|
||||
|
||||
if (state->row == -1) {
|
||||
decor_redraw_start(wp, row, state);
|
||||
} else if (!state->itr_valid) {
|
||||
marktree_itr_get(wp->w_buffer->b_marktree, row, 0, state->itr);
|
||||
state->itr_valid = true;
|
||||
}
|
||||
|
||||
state->row = row;
|
||||
state->col_until = -1;
|
||||
state->eol_col = -1;
|
||||
|
@ -96,6 +96,7 @@ typedef struct {
|
||||
TriState spell;
|
||||
|
||||
bool running_decor_provider;
|
||||
bool itr_valid;
|
||||
} DecorState;
|
||||
|
||||
EXTERN DecorState decor_state INIT( = { 0 });
|
||||
|
@ -155,7 +155,7 @@ void decor_providers_invoke_win(win_T *wp)
|
||||
/// @param row Row to invoke line callback for
|
||||
/// @param[out] has_decor Set when at least one provider invokes a line callback
|
||||
/// @param[out] err Provider error
|
||||
void decor_providers_invoke_line(win_T *wp, int row, bool *has_decor)
|
||||
void decor_providers_invoke_line(win_T *wp, int row)
|
||||
{
|
||||
decor_state.running_decor_provider = true;
|
||||
for (size_t i = 0; i < kv_size(decor_providers); i++) {
|
||||
@ -165,9 +165,7 @@ void decor_providers_invoke_line(win_T *wp, int row, bool *has_decor)
|
||||
ADD_C(args, WINDOW_OBJ(wp->handle));
|
||||
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
|
||||
ADD_C(args, INTEGER_OBJ(row));
|
||||
if (decor_provider_invoke((int)i, "line", p->redraw_line, args, true)) {
|
||||
*has_decor = true;
|
||||
} else {
|
||||
if (!decor_provider_invoke((int)i, "line", p->redraw_line, args, true)) {
|
||||
// return 'false' or error: skip rest of this window
|
||||
kv_A(decor_providers, i).state = kDecorProviderWinDisabled;
|
||||
}
|
||||
|
@ -1051,12 +1051,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|
||||
}
|
||||
}
|
||||
|
||||
has_decor = decor_redraw_line(wp, lnum - 1, &decor_state);
|
||||
|
||||
if (!end_fill) {
|
||||
decor_providers_invoke_line(wp, lnum - 1, &has_decor);
|
||||
decor_providers_invoke_line(wp, lnum - 1);
|
||||
}
|
||||
|
||||
has_decor = decor_redraw_line(wp, lnum - 1, &decor_state);
|
||||
|
||||
if (has_decor) {
|
||||
extra_check = true;
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
|
||||
MTKey mark = { { row, col }, ns_id, id, flags, decor.data };
|
||||
|
||||
marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity);
|
||||
decor_state_invalidate(buf);
|
||||
|
||||
revised:
|
||||
if (decor_flags || decor.ext) {
|
||||
@ -184,6 +185,8 @@ void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore)
|
||||
}
|
||||
}
|
||||
|
||||
decor_state_invalidate(buf);
|
||||
|
||||
// TODO(bfredl): delete it from current undo header, opportunistically?
|
||||
}
|
||||
|
||||
@ -237,6 +240,10 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
|
||||
}
|
||||
}
|
||||
|
||||
if (marks_cleared_any) {
|
||||
decor_state_invalidate(buf);
|
||||
}
|
||||
|
||||
return marks_cleared_any;
|
||||
}
|
||||
|
||||
|
@ -744,6 +744,30 @@ describe('decorations providers', function()
|
||||
]])
|
||||
eq(2, exec_lua([[return _G.cnt]]))
|
||||
end)
|
||||
|
||||
it('can do large changes to the marktree', function()
|
||||
insert("line1 with a lot of text\nline2 with a lot of text")
|
||||
setup_provider([[
|
||||
function on_do(event, _, _, row)
|
||||
if event == 'win' or (event == 'line' and row == 1) then
|
||||
vim.api.nvim_buf_clear_namespace(0, ns1, 0, -1)
|
||||
for i = 0,1 do
|
||||
for j = 0,23 do
|
||||
vim.api.nvim_buf_set_extmark(0, ns1, i, j, {hl_group='ErrorMsg', end_col = j+1})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
]])
|
||||
|
||||
-- Doesn't crash when modifying the marktree between line1 and line2
|
||||
screen:expect([[
|
||||
{2:line1 with a lot of text} |
|
||||
{2:line2 with a lot of tex^t} |
|
||||
{1:~ }|*5
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
local example_text = [[
|
||||
|
Loading…
Reference in New Issue
Block a user