]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0906: slow transstr() with long strings v9.2.0906
authorSamuel Schlesinger <sgschlesinger@gmail.com>
Mon, 3 Aug 2026 20:30:09 +0000 (20:30 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 3 Aug 2026 20:30:09 +0000 (20:30 +0000)
Problem:  transstr() appends with STRCAT()/STRLEN() from the start of
          the result on every iteration, making it quadratic to the
          length of the string.
Solution: Keep a tail pointer and append at it. (Samuel Schlesinger).

closes: #20925

Signed-off-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/charset.c
src/testdir/test_functions.vim
src/version.c

index 1c45688fe3b2c2876a78efe2c1cd285ce8378ee7..3be32f5fb4b4dbaf23c87636fedcf0344b6aba3e 100644 (file)
@@ -383,7 +383,10 @@ transstr(char_u *s)
     if (res == NULL)
        return NULL;
 
-    *res = NUL;
+    // Keep a tail pointer to append to, appending with STRCAT would make
+    // this loop quadratic.
+    char_u *d = res;
+
     p = s;
     while (*p != NUL)
     {
@@ -391,14 +394,28 @@ transstr(char_u *s)
        {
            c = (*mb_ptr2char)(p);
            if (vim_isprintc(c))
-               STRNCAT(res, p, l);     // append printable multi-byte char
+           {
+               // append printable multi-byte char
+               mch_memmove(d, p, (size_t)l);
+               d += l;
+           }
            else
-               transchar_hex(res + STRLEN(res), c);
+           {
+               transchar_hex(d, c);
+               d += STRLEN(d);
+           }
            p += l;
        }
        else
-           STRCAT(res, transchar_byte(*p++));
+       {
+           char_u      *trs = transchar_byte(*p++);
+           int         trs_len = (int)STRLEN(trs);
+
+           mch_memmove(d, trs, (size_t)trs_len);
+           d += trs_len;
+       }
     }
+    *d = NUL;
     return res;
 }
 
index 645ff531ed0d8697a74fa8880f7791145d8413a8..2f2a7fc5f6774700036c5e8634ac9e5e71182885 100644 (file)
@@ -211,6 +211,56 @@ func Test_strwidth()
   set ambiwidth&
 endfunc
 
+func Test_strtrans()
+  " The default of 'isprint' is platform-dependent: 0x7f and 0x9f are
+  " printable on Win32 and VMS.  Set it so the expectations below hold
+  " everywhere.
+  let save_isprint = &isprint
+  set isprint=@,161-255
+
+  " printable ASCII is unchanged
+  call assert_equal('', strtrans(''))
+  call assert_equal('abc', strtrans('abc'))
+
+  " control characters are displayed as ^X
+  call assert_equal('^I', strtrans("\t"))
+  call assert_equal('a^Mb^[c', strtrans("a\rb\ec"))
+  call assert_equal('^A^_^?', strtrans("\x01\x1f\x7f"))
+
+  " printable multibyte characters are unchanged, including composing
+  " characters and characters above 0xffff
+  call assert_equal('héllo 你好', strtrans('héllo 你好'))
+  let s = 'e' .. nr2char(0x301) .. 'x'
+  call assert_equal(s, strtrans(s))
+  call assert_equal(nr2char(0x1d11e), strtrans(nr2char(0x1d11e)))
+
+  " unprintable multibyte characters are displayed in <xx> hex form
+  call assert_equal('<9f>', strtrans(nr2char(0x9f)))
+  call assert_equal('<200b>', strtrans(nr2char(0x200b)))
+  call assert_equal('<feff>', strtrans(nr2char(0xfeff)))
+
+  " illegal bytes are displayed in <xx> hex form
+  call assert_equal('A<ff>B', strtrans("A\xffB"))
+
+  " a long string mixing all kinds of characters
+  call assert_equal(repeat('a^Bé<9f>', 100),
+        \ strtrans(repeat("a\x02é" .. nr2char(0x9f), 100)))
+
+  " the non-multi-byte code path
+  set encoding=latin1
+  set isprint=@,161-255
+  call assert_equal('a^Mb^[c', strtrans("a\rb\ec"))
+  call assert_equal('^A^_^?', strtrans("\x01\x1f\x7f"))
+  " an unprintable byte above 0x7f uses the meta notation
+  call assert_equal('| ', strtrans("\xa0"))
+  " a printable high byte is unchanged
+  call assert_equal("\xe9", strtrans("\xe9"))
+  call assert_equal("x^B\xe9| y", strtrans("x\x02\xe9\xa0y"))
+  set encoding=utf-8
+
+  let &isprint = save_isprint
+endfunc
+
 func Test_str2nr()
   call assert_equal(0, str2nr(''))
   call assert_equal(1, str2nr('1'))
index 7bb915f93224d01f5c927f28f5f300663907214b..13c9fc7d31780ba89215987eb1bbc13bdf60f664 100644 (file)
@@ -763,6 +763,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    906,
 /**/
     905,
 /**/