From 37f47958b8a2a44abc60614271d9537e7f14e51a Mon Sep 17 00:00:00 2001
From: Bram Moolenaar <Bram@vim.org>
Date: Sat, 29 Jan 2022 14:21:51 +0000
Subject: [PATCH] patch 8.2.4253: using freed memory when substitute with
 function call

Problem:    Using freed memory when substitute uses a recursive function call.
Solution:   Make a copy of the substitute text.
---
 src/ex_cmds.c                   | 19 +++++++++++++++----
 src/testdir/test_substitute.vim | 16 ++++++++++++++++
 2 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index cccdf47..aa97b40 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3577,6 +3577,7 @@ do_sub(exarg_T *eap)
     int		save_do_all;		// remember user specified 'g' flag
     int		save_do_ask;		// remember user specified 'c' flag
     char_u	*pat = NULL, *sub = NULL;	// init for GCC
+    char_u	*sub_copy = NULL;
     int		delimiter;
     int		sublen;
     int		got_quit = FALSE;
@@ -3866,11 +3867,20 @@ do_sub(exarg_T *eap)
     sub_firstline = NULL;
 
     /*
-     * ~ in the substitute pattern is replaced with the old pattern.
-     * We do it here once to avoid it to be replaced over and over again.
-     * But don't do it when it starts with "\=", then it's an expression.
+     * If the substitute pattern starts with "\=" then it's an expression.
+     * Make a copy, a recursive function may free it.
+     * Otherwise, '~' in the substitute pattern is replaced with the old
+     * pattern.  We do it here once to avoid it to be replaced over and over
+     * again.
      */
-    if (!(sub[0] == '\\' && sub[1] == '='))
+    if (sub[0] == '\\' && sub[1] == '=')
+    {
+       sub = vim_strsave(sub);
+       if (sub == NULL)
+           return;
+       sub_copy = sub;
+    }
+    else
 	sub = regtilde(sub, p_magic);
 
     /*
@@ -4670,6 +4680,7 @@ outofmem:
 #endif
 
     vim_regfree(regmatch.regprog);
+    vim_free(sub_copy);
 
     // Restore the flag values, they can be used for ":&&".
     subflags.do_all = save_do_all;
diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim
index c7532fb..3e6bc5c 100644
--- a/src/testdir/test_substitute.vim
+++ b/src/testdir/test_substitute.vim
@@ -745,3 +745,19 @@ func Test_sub_beyond_end()
   call assert_equal('#', getline(1))
   bwipe!
 endfunc
+
+" This was using "old_sub" after it was freed.
+func Test_using_old_sub()
+  set compatible maxfuncdepth=10
+  new
+  call setline(1, 'some text.')
+  func Repl()
+    ~
+    s/
+  endfunc
+  silent!  s/\%')/\=Repl()
+
+  delfunc Repl
+  bwipe!
+  set nocompatible
+endfunc
-- 
1.8.3.1

