Skip to content

Commit 44ddf19

Browse files
committed
patch 8.2.5146: memory leak when substitute expression nests
Problem: Memory leak when substitute expression nests. Solution: Use an array of expression results.
1 parent cf801d4 commit 44ddf19

7 files changed

Lines changed: 77 additions & 23 deletions

File tree

src/alloc.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,9 @@ free_all_mem(void)
586586
# ifdef FEAT_QUICKFIX
587587
check_quickfix_busy();
588588
# endif
589+
# ifdef FEAT_EVAL
590+
free_resub_eval_result();
591+
# endif
589592
}
590593
#endif
591594

src/errors.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3300,3 +3300,7 @@ EXTERN char e_could_not_reset_handler_for_timeout_str[]
33003300
EXTERN char e_could_not_check_for_pending_sigalrm_str[]
33013301
INIT(= N_("E1289: Could not check for pending SIGALRM: %s"));
33023302
#endif
3303+
#ifdef FEAT_EVAL
3304+
EXTERN char e_substitute_nesting_too_deep[]
3305+
INIT(= N_("E1290: substitute nesting too deep"));
3306+
#endif

src/ex_cmds.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3701,6 +3701,7 @@ ex_substitute(exarg_T *eap)
37013701
int start_nsubs;
37023702
#ifdef FEAT_EVAL
37033703
int save_ma = 0;
3704+
int save_sandbox = 0;
37043705
#endif
37053706

37063707
cmd = eap->arg;
@@ -4403,6 +4404,7 @@ ex_substitute(exarg_T *eap)
44034404
*/
44044405
#ifdef FEAT_EVAL
44054406
save_ma = curbuf->b_p_ma;
4407+
save_sandbox = sandbox;
44064408
if (subflags.do_count)
44074409
{
44084410
// prevent accidentally changing the buffer by a function
@@ -4416,7 +4418,8 @@ ex_substitute(exarg_T *eap)
44164418
// Disallow changing text or switching window in an expression.
44174419
++textlock;
44184420
#endif
4419-
// get length of substitution part
4421+
// Get length of substitution part, including the NUL.
4422+
// When it fails sublen is zero.
44204423
sublen = vim_regsub_multi(&regmatch,
44214424
sub_firstlnum - regmatch.startpos[0].lnum,
44224425
sub, sub_firstline, 0,
@@ -4429,11 +4432,10 @@ ex_substitute(exarg_T *eap)
44294432
// the replacement.
44304433
// Don't keep flags set by a recursive call.
44314434
subflags = subflags_save;
4432-
if (aborting() || subflags.do_count)
4435+
if (sublen == 0 || aborting() || subflags.do_count)
44334436
{
44344437
curbuf->b_p_ma = save_ma;
4435-
if (sandbox > 0)
4436-
sandbox--;
4438+
sandbox = save_sandbox;
44374439
goto skip;
44384440
}
44394441
#endif

src/proto/regexp.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ void unref_extmatch(reg_extmatch_T *em);
1010
char_u *regtilde(char_u *source, int magic);
1111
int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, int destlen, int flags);
1212
int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int destlen, int flags);
13+
void free_resub_eval_result(void);
1314
char_u *reg_submatch(int no);
1415
list_T *reg_submatch_list(int no);
1516
int vim_regcomp_had_eol(void);

src/regexp.c

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,6 +1922,23 @@ vim_regsub_multi(
19221922
return result;
19231923
}
19241924

1925+
#if defined(FEAT_EVAL) || defined(PROTO)
1926+
// When nesting more than a couple levels it's probably a mistake.
1927+
# define MAX_REGSUB_NESTING 4
1928+
static char_u *eval_result[MAX_REGSUB_NESTING] = {NULL, NULL, NULL, NULL};
1929+
1930+
# if defined(EXITFREE) || defined(PROTO)
1931+
void
1932+
free_resub_eval_result(void)
1933+
{
1934+
int i;
1935+
1936+
for (i = 0; i < MAX_REGSUB_NESTING; ++i)
1937+
VIM_CLEAR(eval_result[i]);
1938+
}
1939+
# endif
1940+
#endif
1941+
19251942
static int
19261943
vim_regsub_both(
19271944
char_u *source,
@@ -1941,7 +1958,8 @@ vim_regsub_both(
19411958
linenr_T clnum = 0; // init for GCC
19421959
int len = 0; // init for GCC
19431960
#ifdef FEAT_EVAL
1944-
static char_u *eval_result = NULL;
1961+
static int nesting = 0;
1962+
int nested;
19451963
#endif
19461964
int copy = flags & REGSUB_COPY;
19471965

@@ -1953,6 +1971,14 @@ vim_regsub_both(
19531971
}
19541972
if (prog_magic_wrong())
19551973
return 0;
1974+
#ifdef FEAT_EVAL
1975+
if (nesting == MAX_REGSUB_NESTING)
1976+
{
1977+
emsg(_(e_substitute_nesting_too_deep));
1978+
return 0;
1979+
}
1980+
nested = nesting;
1981+
#endif
19561982
src = source;
19571983
dst = dest;
19581984

@@ -1969,19 +1995,19 @@ vim_regsub_both(
19691995
// "flags & REGSUB_COPY" != 0.
19701996
if (copy)
19711997
{
1972-
if (eval_result != NULL)
1998+
if (eval_result[nested] != NULL)
19731999
{
1974-
STRCPY(dest, eval_result);
1975-
dst += STRLEN(eval_result);
1976-
VIM_CLEAR(eval_result);
2000+
STRCPY(dest, eval_result[nested]);
2001+
dst += STRLEN(eval_result[nested]);
2002+
VIM_CLEAR(eval_result[nested]);
19772003
}
19782004
}
19792005
else
19802006
{
19812007
int prev_can_f_submatch = can_f_submatch;
19822008
regsubmatch_T rsm_save;
19832009

1984-
VIM_CLEAR(eval_result);
2010+
VIM_CLEAR(eval_result[nested]);
19852011

19862012
// The expression may contain substitute(), which calls us
19872013
// recursively. Make sure submatch() gets the text from the first
@@ -1995,6 +2021,11 @@ vim_regsub_both(
19952021
rsm.sm_maxline = rex.reg_maxline;
19962022
rsm.sm_line_lbr = rex.reg_line_lbr;
19972023

2024+
// Although unlikely, it is possible that the expression invokes a
2025+
// substitute command (it might fail, but still). Therefore keep
2026+
// an array if eval results.
2027+
++nesting;
2028+
19982029
if (expr != NULL)
19992030
{
20002031
typval_T argv[2];
@@ -2034,26 +2065,27 @@ vim_regsub_both(
20342065

20352066
if (rettv.v_type == VAR_UNKNOWN)
20362067
// something failed, no need to report another error
2037-
eval_result = NULL;
2068+
eval_result[nested] = NULL;
20382069
else
20392070
{
2040-
eval_result = tv_get_string_buf_chk(&rettv, buf);
2041-
if (eval_result != NULL)
2042-
eval_result = vim_strsave(eval_result);
2071+
eval_result[nested] = tv_get_string_buf_chk(&rettv, buf);
2072+
if (eval_result[nested] != NULL)
2073+
eval_result[nested] = vim_strsave(eval_result[nested]);
20432074
}
20442075
clear_tv(&rettv);
20452076
}
20462077
else if (substitute_instr != NULL)
20472078
// Execute instructions from ISN_SUBSTITUTE.
2048-
eval_result = exe_substitute_instr();
2079+
eval_result[nested] = exe_substitute_instr();
20492080
else
2050-
eval_result = eval_to_string(source + 2, TRUE);
2081+
eval_result[nested] = eval_to_string(source + 2, TRUE);
2082+
--nesting;
20512083

2052-
if (eval_result != NULL)
2084+
if (eval_result[nested] != NULL)
20532085
{
20542086
int had_backslash = FALSE;
20552087

2056-
for (s = eval_result; *s != NUL; MB_PTR_ADV(s))
2088+
for (s = eval_result[nested]; *s != NUL; MB_PTR_ADV(s))
20572089
{
20582090
// Change NL to CR, so that it becomes a line break,
20592091
// unless called from vim_regexec_nl().
@@ -2077,15 +2109,15 @@ vim_regsub_both(
20772109
if (had_backslash && (flags & REGSUB_BACKSLASH))
20782110
{
20792111
// Backslashes will be consumed, need to double them.
2080-
s = vim_strsave_escaped(eval_result, (char_u *)"\\");
2112+
s = vim_strsave_escaped(eval_result[nested], (char_u *)"\\");
20812113
if (s != NULL)
20822114
{
2083-
vim_free(eval_result);
2084-
eval_result = s;
2115+
vim_free(eval_result[nested]);
2116+
eval_result[nested] = s;
20852117
}
20862118
}
20872119

2088-
dst += STRLEN(eval_result);
2120+
dst += STRLEN(eval_result[nested]);
20892121
}
20902122

20912123
can_f_submatch = prev_can_f_submatch;

src/testdir/test_substitute.vim

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ func Test_using_old_sub()
995995
~
996996
s/
997997
endfunc
998-
silent! s/\%')/\=Repl()
998+
silent! s/\%')/\=Repl()
999999

10001000
delfunc Repl
10011001
bwipe!
@@ -1359,4 +1359,14 @@ func Test_substitute_short_cmd()
13591359
bw!
13601360
endfunc
13611361

1362+
" This should be done last to reveal a memory leak when vim_regsub_both() is
1363+
" called to evaluate an expression but it is not used in a second call.
1364+
func Test_z_substitute_expr_leak()
1365+
func SubExpr()
1366+
~n
1367+
endfunc
1368+
silent! s/\%')/\=SubExpr()
1369+
delfunc SubExpr
1370+
endfunc
1371+
13621372
" vim: shiftwidth=2 sts=2 expandtab

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ static char *(features[]) =
734734

735735
static int included_patches[] =
736736
{ /* Add new patch number below this line */
737+
/**/
738+
5146,
737739
/**/
738740
5145,
739741
/**/

0 commit comments

Comments
 (0)