From 338f1fc0ee3ca929387448fe464579d6113fa76a Mon Sep 17 00:00:00 2001
From: Bram Moolenaar <Bram@vim.org>
Date: Thu, 26 May 2022 15:56:23 +0100
Subject: [PATCH] patch 8.2.5023: substitute overwrites allocated buffer

Problem:    Substitute overwrites allocated buffer.
Solution:   Disallow undo when in a substitute command.
---
 src/normal.c                    | 42 ++++++++++++++++++++---------------------
 src/testdir/test_substitute.vim | 23 ++++++++++++++++++++++
 src/undo.c                      |  6 ++++++
 3 files changed, 50 insertions(+), 21 deletions(-)

diff --git a/src/normal.c b/src/normal.c
index 71c05bb..e294706 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -161,6 +161,22 @@ typedef void (*nv_func_T)(cmdarg_T *cap);
  */
 
 /*
+ * If currently editing a cmdline or text is locked: beep and give an error
+ * message, return TRUE.
+ */
+    static int
+check_text_locked(oparg_T *oap)
+{
+    if (text_locked())
+    {
+       clearopbeep(oap);
+       text_locked_msg();
+       return TRUE;
+    }
+    return FALSE;
+}
+
+/*
  * This table contains one entry for every Normal or Visual mode command.
  * The order doesn't matter, init_normal_cmds() will create a sorted index.
  * It is faster when all keys from zero to '~' are present.
@@ -738,14 +754,9 @@ getcount:
 	goto normal_end;
     }
 
-    if (text_locked() && (nv_cmds[idx].cmd_flags & NV_NCW))
-    {
-	// This command is not allowed while editing a cmdline: beep.
-	clearopbeep(oap);
-	text_locked_msg();
-	goto normal_end;
-    }
-    if ((nv_cmds[idx].cmd_flags & NV_NCW) && curbuf_locked())
+    if ((nv_cmds[idx].cmd_flags & NV_NCW)
+				&& (check_text_locked(oap) || curbuf_locked()))
+	// this command is not allowed now
 	goto normal_end;
 
     /*
@@ -4155,12 +4166,8 @@ nv_gotofile(cmdarg_T *cap)
     char_u	*ptr;
     linenr_T	lnum = -1;
 
-    if (text_locked())
-    {
-	clearopbeep(cap->oap);
-	text_locked_msg();
+    if (check_text_locked(cap->oap))
 	return;
-    }
     if (curbuf_locked())
     {
 	clearop(cap->oap);
@@ -6288,14 +6295,7 @@ nv_g_cmd(cmdarg_T *cap)
 
     // "gQ": improved Ex mode
     case 'Q':
-	if (text_locked())
-	{
-	    clearopbeep(cap->oap);
-	    text_locked_msg();
-	    break;
-	}
-
-	if (!checkclearopq(oap))
+	if (!check_text_locked(cap->oap) && !checkclearopq(oap))
 	    do_exmode(TRUE);
 	break;
 
diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim
index 3450c4f..b1d1bde 100644
--- a/src/testdir/test_substitute.vim
+++ b/src/testdir/test_substitute.vim
@@ -801,3 +801,26 @@ func Test_sub_open_cmdline_win()
   call delete('Xscript')
   call delete('Xresult')
 endfunc
+
+" This was undoign a change in between computing the length and using it.
+func Do_Test_sub_undo_change()
+  new
+  norm o0000000000000000000000000000000000000000000000000000
+  silent! s/\%')/\=Repl()
+  bwipe!
+endfunc
+
+func Test_sub_undo_change()
+  func Repl()
+    silent! norm g-
+  endfunc
+  call Do_Test_sub_undo_change()
+
+  func! Repl()
+    silent earlier
+  endfunc
+  call Do_Test_sub_undo_change()
+
+  delfunc Repl
+endfunc
+
diff --git a/src/undo.c b/src/undo.c
index 3dcf277..b3a91b5 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -2283,6 +2283,12 @@ undo_time(
     int		    above = FALSE;
     int		    did_undo = TRUE;
 
+    if (text_locked())
+    {
+	text_locked_msg();
+	return;
+    }
+
     // First make sure the current undoable change is synced.
     if (curbuf->b_u_synced == FALSE)
 	u_sync(TRUE);
-- 
1.8.3.1

