# HG changeset patch # User Bram Moolenaar # Date 1657219507 -7200 # Node ID 979ce206409aa438a1d583ceb844e66003c7fb80 # Parent 3372fc2a122c6b96e2d34c6280a774751c16a7b7 patch 9.0.0045: reading past end of completion with a long line Commit: https://github.com/vim/vim/commit/caea66442d86e7bbba3bf3dc202c3c0d549b9853 Author: Bram Moolenaar Date: Thu Jul 7 19:42:04 2022 +0100 patch 9.0.0045: reading past end of completion with a long line Problem: Reading past end of completion with a long line and 'infercase' set. Solution: Allocate the string if needed. diff --git a/src/insexpand.c b/src/insexpand.c --- a/src/insexpand.c +++ b/src/insexpand.c @@ -524,29 +524,32 @@ ins_compl_accept_char(int c) /* * Get the completed text by inferring the case of the originally typed text. + * If the result is in allocated memory "tofree" is set to it. */ static char_u * ins_compl_infercase_gettext( char_u *str, - int actual_len, - int actual_compl_length, - int min_len) + int char_len, + int compl_char_len, + int min_len, + char_u **tofree) { int *wca; // Wide character array. char_u *p; int i, c; int has_lower = FALSE; int was_letter = FALSE; + garray_T gap; IObuff[0] = NUL; // Allocate wide character array for the completion and fill it. - wca = ALLOC_MULT(int, actual_len); + wca = ALLOC_MULT(int, char_len); if (wca == NULL) return IObuff; p = str; - for (i = 0; i < actual_len; ++i) + for (i = 0; i < char_len; ++i) if (has_mbyte) wca[i] = mb_ptr2char_adv(&p); else @@ -566,7 +569,7 @@ ins_compl_infercase_gettext( if (MB_ISUPPER(wca[i])) { // Rule 1 is satisfied. - for (i = actual_compl_length; i < actual_len; ++i) + for (i = compl_char_len; i < char_len; ++i) wca[i] = MB_TOLOWER(wca[i]); break; } @@ -587,7 +590,7 @@ ins_compl_infercase_gettext( if (was_letter && MB_ISUPPER(c) && MB_ISLOWER(wca[i])) { // Rule 2 is satisfied. - for (i = actual_compl_length; i < actual_len; ++i) + for (i = compl_char_len; i < char_len; ++i) wca[i] = MB_TOUPPER(wca[i]); break; } @@ -610,20 +613,52 @@ ins_compl_infercase_gettext( } // Generate encoding specific output from wide character array. - // Multi-byte characters can occupy up to five bytes more than - // ASCII characters, and we also need one byte for NUL, so stay - // six bytes away from the edge of IObuff. p = IObuff; i = 0; - while (i < actual_len && (p - IObuff + 6) < IOSIZE) - if (has_mbyte) + ga_init2(&gap, 1, 500); + while (i < char_len) + { + if (gap.ga_data != NULL) + { + if (ga_grow(&gap, 10) == FAIL) + { + ga_clear(&gap); + return (char_u *)"[failed]"; + } + p = (char_u *)gap.ga_data + gap.ga_len; + if (has_mbyte) + gap.ga_len += (*mb_char2bytes)(wca[i++], p); + else + { + *p = wca[i++]; + ++gap.ga_len; + } + } + else if ((p - IObuff) + 6 >= IOSIZE) + { + // Multi-byte characters can occupy up to five bytes more than + // ASCII characters, and we also need one byte for NUL, so when + // getting to six bytes from the edge of IObuff switch to using a + // growarray. Add the character in the next round. + if (ga_grow(&gap, IOSIZE) == FAIL) + return (char_u *)"[failed]"; + STRCPY(gap.ga_data, IObuff); + gap.ga_len = STRLEN(IObuff); + } + else if (has_mbyte) p += (*mb_char2bytes)(wca[i++], p); else *(p++) = wca[i++]; + } + vim_free(wca); + + if (gap.ga_data != NULL) + { + *tofree = gap.ga_data; + return gap.ga_data; + } + *p = NUL; - - vim_free(wca); - return IObuff; } @@ -644,10 +679,12 @@ ins_compl_add_infercase( { char_u *str = str_arg; char_u *p; - int actual_len; // Take multi-byte characters - int actual_compl_length; // into account. + int char_len; // count multi-byte characters + int compl_char_len; int min_len; int flags = 0; + int res; + char_u *tofree = NULL; if (p_ic && curbuf->b_p_inf && len > 0) { @@ -657,44 +694,45 @@ ins_compl_add_infercase( if (has_mbyte) { p = str; - actual_len = 0; + char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - ++actual_len; + ++char_len; } } else - actual_len = len; + char_len = len; // Find actual length of original text. if (has_mbyte) { p = compl_orig_text; - actual_compl_length = 0; + compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - ++actual_compl_length; + ++compl_char_len; } } else - actual_compl_length = compl_length; - - // "actual_len" may be smaller than "actual_compl_length" when using + compl_char_len = compl_length; + + // "char_len" may be smaller than "compl_char_len" when using // thesaurus, only use the minimum when comparing. - min_len = actual_len < actual_compl_length - ? actual_len : actual_compl_length; - - str = ins_compl_infercase_gettext(str, actual_len, actual_compl_length, - min_len); + min_len = char_len < compl_char_len ? char_len : compl_char_len; + + str = ins_compl_infercase_gettext(str, char_len, + compl_char_len, min_len, &tofree); } if (cont_s_ipos) flags |= CP_CONT_S_IPOS; if (icase) flags |= CP_ICASE; - return ins_compl_add(str, len, fname, NULL, NULL, dir, flags, FALSE); + res = ins_compl_add(str, len, fname, NULL, NULL, dir, flags, FALSE); + vim_free(tofree); + return res; } /* diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -2097,4 +2097,20 @@ func Test_complete_overrun() bwipe! endfunc +func Test_infercase_very_long_line() + " this was truncating the line when inferring case + new + let longLine = "blah "->repeat(300) + let verylongLine = "blah "->repeat(400) + call setline(1, verylongLine) + call setline(2, longLine) + set ic infercase + exe "normal 2Go\\\" + call assert_equal(longLine, getline(3)) + + bwipe! + set noic noinfercase +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -736,6 +736,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 45, +/**/ 44, /**/ 43,