Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ free_all_mem(void)
free_homedir();
free_users();
free_search_patterns();
# ifdef FEAT_EVAL
free_eval_regcomp_cache();
# endif
free_old_sub();
free_last_insert();
free_insexpand_stuff();
Expand Down
109 changes: 107 additions & 2 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3188,6 +3188,111 @@ set_context_for_expression(
xp->xp_pattern = arg;
}

// Cache with the compiled program of the last pattern used by
// pattern_match() and the match functions. Script loops often evaluate the
// same pattern many times; reusing the program avoids compiling it for
// every evaluation. The cache owns the program only between uses:
// eval_regcomp() hands it to the caller and empties the cache, and
// eval_regfree() adopts the program the caller ends up with. Thus when
// executing replaced the program (the automatic engine falling back to
// backtracking frees the original) no freed program is left behind in the
// cache.
static regprog_T *eval_prog_cache = NULL;
static char_u *eval_prog_pat = NULL; // pattern it was compiled for
static char_u *eval_prog_enc = NULL; // 'encoding' when compiled
static long eval_prog_re; // 'regexpengine' when compiled

/*
* Return TRUE when compiling "pat" depends on more state than the cache key
* covers: "~" is replaced with the previous substitute string, and bracket
* classes like [:alpha:], [=a=] and [.a.] can depend on the locale.
*/
static int
eval_prog_volatile(char_u *pat)
{
char_u *p;

if (vim_strchr(pat, '~') != NULL)
return TRUE;
for (p = pat; *p != NUL; ++p)
if (p[0] == '[' && (p[1] == ':' || p[1] == '=' || p[1] == '.'))
return TRUE;
return FALSE;
}

/*
* Compile pattern "pat" like vim_regcomp(pat, RE_MAGIC + RE_STRING) would,
* but reuse the cached program when it was compiled for the same pattern.
* Free the result with eval_regfree(), not with vim_regfree().
*/
regprog_T *
eval_regcomp(char_u *pat)
{
if (eval_prog_cache != NULL
&& eval_prog_re == p_re
&& STRCMP(eval_prog_pat, pat) == 0
&& STRCMP(eval_prog_enc, p_enc) == 0)
{
regprog_T *prog = eval_prog_cache;

// The caller now owns the program, eval_regfree() adopts the
// program the caller ends up with.
eval_prog_cache = NULL;
return prog;
}
return vim_regcomp(pat, RE_MAGIC + RE_STRING);
}

/*
* Free program "prog", obtained with eval_regcomp() for pattern "pat", by
* keeping it in the cache for the next use.
*/
void
eval_regfree(char_u *pat, regprog_T *prog)
{
if (prog == NULL)
return;
if (eval_prog_volatile(pat))
{
// compiling depends on state the cache key does not cover
vim_regfree(prog);
return;
}
if (eval_prog_pat == NULL || STRCMP(eval_prog_pat, pat) != 0
|| STRCMP(eval_prog_enc, p_enc) != 0)
{
char_u *pat_copy = vim_strsave(pat);
char_u *enc_copy = vim_strsave(p_enc);

if (pat_copy == NULL || enc_copy == NULL)
{
vim_free(pat_copy);
vim_free(enc_copy);
vim_regfree(prog);
return;
}
vim_free(eval_prog_pat);
vim_free(eval_prog_enc);
eval_prog_pat = pat_copy;
eval_prog_enc = enc_copy;
}
// A nested evaluation may have filled the cache, keep the most recent.
vim_regfree(eval_prog_cache);
eval_prog_cache = prog;
eval_prog_re = p_re;
}

#if defined(EXITFREE) || defined(PROTO)
void
free_eval_regcomp_cache(void)
{
vim_regfree(eval_prog_cache);
eval_prog_cache = NULL;
VIM_CLEAR(eval_prog_pat);
VIM_CLEAR(eval_prog_enc);
}
#endif

/*
* Return TRUE if "pat" matches "text".
* Does not use 'cpo' and always uses 'magic'.
Expand All @@ -3202,12 +3307,12 @@ pattern_match(char_u *pat, char_u *text, int ic)
// avoid 'l' flag in 'cpoptions'
save_cpo = p_cpo;
p_cpo = empty_option;
regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
regmatch.regprog = eval_regcomp(pat);
if (regmatch.regprog != NULL)
{
regmatch.rm_ic = ic;
matches = vim_regexec_nl(&regmatch, text, (colnr_T)0);
vim_regfree(regmatch.regprog);
eval_regfree(pat, regmatch.regprog);
}
p_cpo = save_cpo;
return matches;
Expand Down
16 changes: 8 additions & 8 deletions src/evalfunc.c
Original file line number Diff line number Diff line change
Expand Up @@ -9353,7 +9353,7 @@ find_some_match(typval_T *argvars, typval_T *rettv, matchtype_T type)
goto theend;
}

regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
regmatch.regprog = eval_regcomp(pat);
if (regmatch.regprog != NULL)
{
regmatch.rm_ic = p_ic;
Expand Down Expand Up @@ -9460,7 +9460,7 @@ find_some_match(typval_T *argvars, typval_T *rettv, matchtype_T type)
rettv->vval.v_number += (varnumber_T)(str - expr);
}
}
vim_regfree(regmatch.regprog);
eval_regfree(pat, regmatch.regprog);
}

theend:
Expand Down Expand Up @@ -9634,7 +9634,7 @@ f_matchbufline(typval_T *argvars, typval_T *rettv)
save_cpo = p_cpo;
p_cpo = empty_option;

regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
regmatch.regprog = eval_regcomp(pat);
if (regmatch.regprog == NULL)
goto theend;
regmatch.rm_ic = p_ic;
Expand All @@ -9649,7 +9649,7 @@ f_matchbufline(typval_T *argvars, typval_T *rettv)
}

cleanup:
vim_regfree(regmatch.regprog);
eval_regfree(pat, regmatch.regprog);

theend:
p_cpo = save_cpo;
Expand Down Expand Up @@ -9725,7 +9725,7 @@ f_matchstrlist(typval_T *argvars, typval_T *rettv)
save_cpo = p_cpo;
p_cpo = empty_option;

regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
regmatch.regprog = eval_regcomp(pat);
if (regmatch.regprog == NULL)
goto theend;
regmatch.rm_ic = p_ic;
Expand Down Expand Up @@ -9764,7 +9764,7 @@ f_matchstrlist(typval_T *argvars, typval_T *rettv)
}

cleanup:
vim_regfree(regmatch.regprog);
eval_regfree(pat, regmatch.regprog);

theend:
p_cpo = save_cpo;
Expand Down Expand Up @@ -12314,7 +12314,7 @@ f_split(typval_T *argvars, typval_T *rettv)
if (typeerr)
goto theend;

regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
regmatch.regprog = eval_regcomp(pat);
if (regmatch.regprog != NULL)
{
regmatch.rm_ic = FALSE;
Expand Down Expand Up @@ -12346,7 +12346,7 @@ f_split(typval_T *argvars, typval_T *rettv)
str = regmatch.endp[0];
}

vim_regfree(regmatch.regprog);
eval_regfree(pat, regmatch.regprog);
}

theend:
Expand Down
3 changes: 3 additions & 0 deletions src/proto/eval.pro
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ void skip_for_lines(void *fi_void, evalarg_T *evalarg);
int next_for_item(void *fi_void, char_u *arg);
void free_for_info(void *fi_void);
void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx);
regprog_T *eval_regcomp(char_u *pat);
void eval_regfree(char_u *pat, regprog_T *prog);
void free_eval_regcomp_cache(void);
int pattern_match(char_u *pat, char_u *text, int ic);
char_u *eval_next_non_blank(char_u *arg, evalarg_T *evalarg, int *getnext);
char_u *eval_next_line(char_u *arg, evalarg_T *evalarg);
Expand Down
46 changes: 46 additions & 0 deletions src/testdir/test_eval_stuff.vim
Original file line number Diff line number Diff line change
Expand Up @@ -1385,4 +1385,50 @@ func Test_clipboard_provider_recursive()
unlet g:vim_copy_recursive
endfunc

" Test that caching the compiled pattern of "=~" and the match functions
" does not change the semantics.
func Test_eval_pattern_cache()
" "~" in a pattern stands for the previous substitute string, a compiled
" program must not be reused across a :substitute
new
call setline(1, 'one')
s/one/AAA/
call assert_true('xAAAy' =~ 'x~y')
call assert_false('xBBBy' =~ 'x~y')
call setline(1, 'AAA')
s/AAA/BBB/
call assert_true('xBBBy' =~ 'x~y')
call assert_false('xAAAy' =~ 'x~y')
bwipe!

" 'ignorecase' is applied at execution time, also with a cached program
set noignorecase
call assert_false('ABC' =~ 'abc')
set ignorecase
call assert_true('ABC' =~ 'abc')
call assert_true('ABC' =~ 'abc')
set noignorecase
call assert_false('ABC' =~ 'abc')

" changing 'regexpengine' compiles the pattern again
for re in [0, 1, 2]
exe 'set re=' .. re
call assert_true('abc123' =~ 'a\+bc\d\+')
call assert_false('xyz' =~ 'a\+bc\d\+')
endfor
set re&

" when the automatic engine replaces the program by falling back to the
" backtracking engine, the replaced program is the one that is kept
call test_override('nfa_fail', 1)
for i in range(3)
call assert_true('fallback' =~ 'fall\%(back\)\?')
call assert_false('nomatch' =~ 'fall\%(back\)\?')
endfor
call test_override('nfa_fail', 0)
for i in range(3)
call assert_true('fallback' =~ 'fall\%(back\)\?')
endfor
endfunc

" vim: shiftwidth=2 sts=2 expandtab
Loading