patch 9.1.1410: out-of-bounds access with 'completefunc' Commit: https://github.com/vim/vim/commit/7c621052c3f180c1ef70fb7abfdad18245f47fcc Author: Girish Palya <giris...@gmail.com> Date: Mon May 26 19:41:59 2025 +0200
patch 9.1.1410: out-of-bounds access with 'completefunc' Problem: out-of-bounds access with 'completefunc' (csetc) Solution: check if it is safe to advance cpt_sources_index (Girish Palya) fixes: #17363 closes: #17374 Co-authored-by: @csetc Signed-off-by: Girish Palya <giris...@gmail.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/src/insexpand.c b/src/insexpand.c index 700f734cb..3592fdcaf 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -227,7 +227,7 @@ typedef struct cpt_source_T static cpt_source_T *cpt_sources_array; // Pointer to the array of completion sources static int cpt_sources_count; // Total number of completion sources specified in the 'cpt' option -static int cpt_sources_index; // Index of the current completion source being expanded +static int cpt_sources_index = -1; // Index of the current completion source being expanded // "compl_match_array" points the currently displayed list of entries in the // popup menu. It is NULL when there is no popup menu. @@ -3949,6 +3949,19 @@ thesaurus_func_complete(int type UNUSED) #endif } +/* + * Check if 'cpt' list index can be advanced to the next completion source. + */ + static int +may_advance_cpt_index(char_u *cpt) +{ + char_u *p = cpt; + + while (*p == ',' || *p == ' ') // Skip delimiters + p++; + return (*p != NUL); +} + /* * Return value of process_next_cpt_value() */ @@ -4004,12 +4017,14 @@ process_next_cpt_value( ins_compl_next_state_T *st, int *compl_type_arg, pos_T *start_match_pos, - int fuzzy_collect) + int fuzzy_collect, + int *advance_cpt_idx) { int compl_type = -1; int status = INS_COMPL_CPT_OK; st->found_all = FALSE; + *advance_cpt_idx = FALSE; while (*st->e_cpt == ',' || *st->e_cpt == ' ') st->e_cpt++; @@ -4124,6 +4139,7 @@ process_next_cpt_value( // in any case e_cpt is advanced to the next entry (void)copy_option_part(&st->e_cpt, IObuff, IOSIZE, ","); + *advance_cpt_idx = may_advance_cpt_index(st->e_cpt); st->found_all = TRUE; if (compl_type == -1) @@ -4976,6 +4992,23 @@ strip_caret_numbers_in_place(char_u *str) *write = ' } +/* + * Safely advance the cpt_sources_index by one. + */ + static int +advance_cpt_sources_index_safe(void) +{ + if (cpt_sources_index < cpt_sources_count - 1) + { + cpt_sources_index++; + return OK; + } +#ifdef FEAT_EVAL + semsg(_(e_list_index_out_of_range_nr), cpt_sources_index + 1); +#endif + return FAIL; +} + /* * Get the next expansion(s), using "compl_pattern". * The search starts at position "ini" in curbuf and in the direction @@ -4993,6 +5026,7 @@ ins_compl_get_exp(pos_T *ini) int i; int found_new_match; int type = ctrl_x_mode; + int may_advance_cpt_idx = FALSE; if (!compl_started) { @@ -5027,7 +5061,8 @@ ins_compl_get_exp(pos_T *ini) ? &st.last_match_pos : &st.first_match_pos; // For ^N/^P loop over all the flags/windows/buffers in 'complete'. - for (cpt_sources_index = 0;;) + cpt_sources_index = 0; + for (;;) { found_new_match = FAIL; st.set_match_pos = FALSE; @@ -5038,13 +5073,15 @@ ins_compl_get_exp(pos_T *ini) if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) && (!compl_started || st.found_all)) { - int status = process_next_cpt_value(&st, &type, ini, cfc_has_mode()); + int status = process_next_cpt_value(&st, &type, ini, + cfc_has_mode(), &may_advance_cpt_idx); if (status == INS_COMPL_CPT_END) break; if (status == INS_COMPL_CPT_CONT) { - cpt_sources_index++; + if (may_advance_cpt_idx && !advance_cpt_sources_index_safe()) + break; continue; } } @@ -5057,8 +5094,8 @@ ins_compl_get_exp(pos_T *ini) // get the next set of completion matches found_new_match = get_next_completion_match(type, &st, ini); - if (type > 0) - cpt_sources_index++; + if (may_advance_cpt_idx && !advance_cpt_sources_index_safe()) + break; // break the loop for specialized modes (use 'complete' just for the // generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new @@ -6907,7 +6944,7 @@ cpt_compl_refresh(void) strip_caret_numbers_in_place(cpt); cpt_sources_index = 0; - for (p = cpt; *p; cpt_sources_index++) + for (p = cpt; *p;) { while (*p == ',' || *p == ' ') // Skip delimiters p++; @@ -6926,7 +6963,9 @@ cpt_compl_refresh(void) } } - copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p + (void)copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p + if (may_advance_cpt_index(p)) + (void)advance_cpt_sources_index_safe(); } cpt_sources_index = -1; diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 06ab6cfa9..ffe549ab5 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -1037,7 +1037,7 @@ func Test_completefunc_invalid_data() exe "normal i\<C-N>" call assert_equal('moon', getline(1)) set completefunc& complete& - close! + bw! endfunc " Test for errors in using complete() function @@ -4126,6 +4126,11 @@ func Test_complete_match_count() exe "normal! Gof\<c-n>\<c-r>=PrintMenuWords()\<cr>" call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5)) 5d + set cpt=.^1,,,F^2,,, + call setline(1, ["fo", "foo", "foobar", "fobarbaz"]) + exe "normal! Gof\<c-n>\<c-r>=PrintMenuWords()\<cr>" + call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5)) + 5d exe "normal! Gof\<c-n>\<c-n>\<c-r>=PrintMenuWords()\<cr>" call assert_equal('foo1{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 1}', getline(5)) 5d @@ -4602,4 +4607,41 @@ func Test_register_completion() set ignorecase& endfunc +" Test refresh:always with unloaded buffers (issue #17363) +func Test_complete_unloaded_buf_refresh_always() + func TestComplete(findstart, base) + if a:findstart + let line = getline('.') + let start = col('.') - 1 + while start > 0 && line[start - 1] =~ ' ' + let start -= 1 + endwhile + return start + else + let g:CallCount += 1 + let res = ["update1", "update12", "update123"] + return #{words: res, refresh: 'always'} + endif + endfunc + + let g:CallCount = 0 + set completeopt=menu,longest + set completefunc=TestComplete + set complete=b,u,t,i,F + badd foo1 + badd foo2 + new + exe "normal! iup\<C-N>\<BS>\<BS>\<BS>\<BS>\<BS>" + call assert_equal('up', getline(1)) + call assert_equal(6, g:CallCount) + + bd! foo1 + bd! foo2 + bw! + set completeopt& + set complete& + set completefunc& + delfunc TestComplete +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/src/version.c b/src/version.c index a9d95e3fe..33b08bb3a 100644 --- a/src/version.c +++ b/src/version.c @@ -709,6 +709,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1410, /**/ 1409, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1uJc7I-00H4PR-UR%40256bit.org.