FROM CAEA66442D86E7BBBA3BF3DC202C3C0D549B9853 MON SEP 17 00:00:00 2001
From: Bram Moolenaar <Bram@vim.org>
Date: Thu, 7 Jul 2022 19:42:04 +0100
Subject: [PATCH] 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.
---
 src/insexpand.c                   | 94 ++++++++++++++++++++++---------
 src/testdir/test_ins_complete.vim | 16 ++++++
 2 files changed, 82 insertions(+), 28 deletions(-)

diff --git a/src/insexpand.c b/src/insexpand.c
index 734550f..0ecb656 100644
--- 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++];
-    *p = NUL;
-
+    }
     vim_free(wca);
 
+    if (gap.ga_data != NULL)
+    {
+	*tofree = gap.ga_data;
+	return gap.ga_data;
+    }
+
+    *p = NUL;
     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;
+	    compl_char_len = compl_length;
 
-	// "actual_len" may be smaller than "actual_compl_length" when using
+	// "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;
+	min_len = char_len < compl_char_len ? char_len : compl_char_len;
 
-	str = ins_compl_infercase_gettext(str, actual_len, actual_compl_length,
-								min_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
index 20c2b4f..f2daa02 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -2192,4 +2192,20 @@ func Test_complete_overrun()
 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\<C-X>\<C-L>\<Esc>"
+  call assert_equal(longLine, getline(3))
+
+  bwipe!
+  set noic noinfercase
+endfunc
+
+
 " vim: shiftwidth=2 sts=2 expandtab
-- 
2.36.1

