# HG changeset patch # User Christian Brabandt # Date 1497818704 -7200 # Node ID 998d2cf59caa9b100c9277e887947287def3b58a # Parent 5d3f98e7f715d3df27b1642611db8bbab7b8c4d3 patch 8.0.0647: syntax highlighting can make cause a freeze commit https://github.com/vim/vim/commit/06f1ed2f78c5c03af95054fc3a8665df39dec362 Author: Bram Moolenaar Date: Sun Jun 18 22:41:03 2017 +0200 patch 8.0.0647: syntax highlighting can make cause a freeze Problem: Syntax highlighting can make cause a freeze. Solution: Apply 'redrawtime' to syntax highlighting, per window. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 8.0. Last change: 2017 Jun 13 +*options.txt* For Vim version 8.0. Last change: 2017 Jun 18 VIM REFERENCE MANUAL by Bram Moolenaar @@ -5945,10 +5945,14 @@ A jump table for the options with a shor {only available when compiled with the |+reltime| feature} The time in milliseconds for redrawing the display. This applies to - searching for patterns for 'hlsearch' and |:match| highlighting. + searching for patterns for 'hlsearch', |:match| highlighting an syntax + highlighting. When redrawing takes more than this many milliseconds no further - matches will be highlighted. This is used to avoid that Vim hangs - when using a very complicated pattern. + matches will be highlighted. + For syntax highlighting the time applies per window. When over the + limit syntax highlighting is disabled until |CTRL-L| is used. + This is used to avoid that Vim hangs when using a very complicated + pattern. *'regexpengine'* *'re'* 'regexpengine' 're' number (default 0) diff --git a/src/normal.c b/src/normal.c --- a/src/normal.c +++ b/src/normal.c @@ -5477,6 +5477,14 @@ nv_clear(cmdarg_T *cap) #ifdef FEAT_SYN_HL /* Clear all syntax states to force resyncing. */ syn_stack_free_all(curwin->w_s); +# ifdef FEAT_RELTIME + { + win_T *wp; + + FOR_ALL_WINDOWS(wp) + wp->w_s->b_syn_slow = FALSE; + } +# endif #endif redraw_later(CLEAR); } diff --git a/src/proto/syntax.pro b/src/proto/syntax.pro --- a/src/proto/syntax.pro +++ b/src/proto/syntax.pro @@ -1,5 +1,5 @@ /* syntax.c */ -void syntax_start(win_T *wp, linenr_T lnum); +void syntax_start(win_T *wp, linenr_T lnum, proftime_T *syntax_tm); void syn_stack_free_all(synblock_T *block); void syn_stack_apply_changes(buf_T *buf); void syntax_end_parsing(linenr_T lnum); diff --git a/src/regexp.c b/src/regexp.c --- a/src/regexp.c +++ b/src/regexp.c @@ -5756,8 +5756,6 @@ regmatch( printf("Premature EOL\n"); #endif } - if (status == RA_FAIL) - got_int = TRUE; return (status == RA_MATCH); } @@ -8224,8 +8222,6 @@ report_re_switch(char_u *pat) } #endif -static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, int nl); - /* * Match a regexp against a string. * "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). @@ -8236,7 +8232,7 @@ static int vim_regexec_both(regmatch_T * * Return TRUE if there is a match, FALSE if not. */ static int -vim_regexec_both( +vim_regexec_string( regmatch_T *rmp, char_u *line, /* string to match against */ colnr_T col, /* column to start looking for match */ @@ -8299,12 +8295,12 @@ vim_regexec_prog( char_u *line, colnr_T col) { - int r; - regmatch_T regmatch; + int r; + regmatch_T regmatch; regmatch.regprog = *prog; regmatch.rm_ic = ignore_case; - r = vim_regexec_both(®match, line, col, FALSE); + r = vim_regexec_string(®match, line, col, FALSE); *prog = regmatch.regprog; return r; } @@ -8316,7 +8312,7 @@ vim_regexec_prog( int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col) { - return vim_regexec_both(rmp, line, col, FALSE); + return vim_regexec_string(rmp, line, col, FALSE); } #if defined(FEAT_MODIFY_FNAME) || defined(FEAT_EVAL) \ @@ -8329,7 +8325,7 @@ vim_regexec(regmatch_T *rmp, char_u *lin int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) { - return vim_regexec_both(rmp, line, col, TRUE); + return vim_regexec_string(rmp, line, col, TRUE); } #endif diff --git a/src/screen.c b/src/screen.c --- a/src/screen.c +++ b/src/screen.c @@ -124,7 +124,7 @@ static void fold_line(win_T *wp, long fo static void fill_foldcolumn(char_u *p, win_T *wp, int closed, linenr_T lnum); static void copy_text_attr(int off, char_u *buf, int len, int attr); #endif -static int win_line(win_T *, linenr_T, int, int, int nochange); +static int win_line(win_T *, linenr_T, int, int, int nochange, proftime_T *syntax_tm); static int char_needs_redraw(int off_from, int off_to, int cols); #ifdef FEAT_RIGHTLEFT static void screen_line(int row, int coloff, int endcol, int clear_width, int rlflag); @@ -185,6 +185,11 @@ static void win_redr_ruler(win_T *wp, in static int screen_char_attr = 0; #endif +#if defined(FEAT_SYN_HL) && defined(FEAT_RELTIME) +/* Can limit syntax highlight time to 'redrawtime'. */ +# define SYN_TIME_LIMIT 1 +#endif + /* * Redraw the current window later, with update_screen(type). * Set must_redraw only if not already set to a higher value. @@ -923,6 +928,9 @@ update_single_line(win_T *wp, linenr_T l { int row; int j; +#ifdef SYN_TIME_LIMIT + proftime_T syntax_tm; +#endif /* Don't do anything if the screen structures are (not yet) valid. */ if (!screen_valid(TRUE) || updating_screen) @@ -931,6 +939,10 @@ update_single_line(win_T *wp, linenr_T l if (lnum >= wp->w_topline && lnum < wp->w_botline && foldedCount(wp, lnum, &win_foldinfo) == 0) { +#ifdef SYN_TIME_LIMIT + /* Set the time limit to 'redrawtime'. */ + profile_setlimit(p_rdt, &syntax_tm); +#endif update_prepare(); row = 0; @@ -944,7 +956,13 @@ update_single_line(win_T *wp, linenr_T l start_search_hl(); prepare_search_hl(wp, lnum); # endif - win_line(wp, lnum, row, row + wp->w_lines[j].wl_size, FALSE); + win_line(wp, lnum, row, row + wp->w_lines[j].wl_size, FALSE, +#ifdef SYN_TIME_LIMIT + &syntax_tm +#else + NULL +#endif + ); # if defined(FEAT_SEARCH_EXTRA) end_search_hl(); # endif @@ -1140,6 +1158,9 @@ win_update(win_T *wp) #if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA) int save_got_int; #endif +#ifdef SYN_TIME_LIMIT + proftime_T syntax_tm; +#endif type = wp->w_redr_type; @@ -1792,6 +1813,10 @@ win_update(win_T *wp) save_got_int = got_int; got_int = 0; #endif +#ifdef SYN_TIME_LIMIT + /* Set the time limit to 'redrawtime'. */ + profile_setlimit(p_rdt, &syntax_tm); +#endif #ifdef FEAT_FOLDING win_foldinfo.fi_level = 0; #endif @@ -2086,7 +2111,13 @@ win_update(win_T *wp) /* * Display one line. */ - row = win_line(wp, lnum, srow, wp->w_height, mod_top == 0); + row = win_line(wp, lnum, srow, wp->w_height, mod_top == 0, +#ifdef SYN_TIME_LIMIT + &syntax_tm +#else + NULL +#endif + ); #ifdef FEAT_FOLDING wp->w_lines[idx].wl_folded = FALSE; @@ -2957,7 +2988,8 @@ win_line( linenr_T lnum, int startrow, int endrow, - int nochange UNUSED) /* not updating for changed text */ + int nochange UNUSED, /* not updating for changed text */ + proftime_T *syntax_tm) { int col = 0; /* visual column on screen */ unsigned off; /* offset in ScreenLines/ScreenAttrs */ @@ -3158,20 +3190,29 @@ win_line( extra_check = 0; #endif #ifdef FEAT_SYN_HL - if (syntax_present(wp) && !wp->w_s->b_syn_error) + if (syntax_present(wp) && !wp->w_s->b_syn_error +# ifdef SYN_TIME_LIMIT + && !wp->w_s->b_syn_slow +# endif + ) { /* Prepare for syntax highlighting in this line. When there is an * error, stop syntax highlighting. */ save_did_emsg = did_emsg; did_emsg = FALSE; - syntax_start(wp, lnum); + syntax_start(wp, lnum, syntax_tm); if (did_emsg) wp->w_s->b_syn_error = TRUE; else { did_emsg = save_did_emsg; - has_syntax = TRUE; - extra_check = TRUE; +#ifdef SYN_TIME_LIMIT + if (!wp->w_s->b_syn_slow) +#endif + { + has_syntax = TRUE; + extra_check = TRUE; + } } } @@ -3548,7 +3589,7 @@ win_line( # ifdef FEAT_SYN_HL /* Need to restart syntax highlighting for this line. */ if (has_syntax) - syntax_start(wp, lnum); + syntax_start(wp, lnum, syntax_tm); # endif } #endif @@ -4491,6 +4532,10 @@ win_line( } else did_emsg = save_did_emsg; +#ifdef SYN_TIME_LIMIT + if (wp->w_s->b_syn_slow) + has_syntax = FALSE; +#endif /* Need to get the line again, a multi-line regexp may * have made it invalid. */ diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1797,6 +1797,9 @@ typedef struct { hashtab_T b_keywtab; /* syntax keywords hash table */ hashtab_T b_keywtab_ic; /* idem, ignore case */ int b_syn_error; /* TRUE when error occurred in HL */ +# ifdef FEAT_RELTIME + int b_syn_slow; /* TRUE when 'redrawtime' reached */ +# endif int b_syn_ic; /* ignore case for :syn cmds */ int b_syn_spell; /* SYNSPL_ values */ garray_T b_syn_patterns; /* table for syntax patterns */ diff --git a/src/syntax.c b/src/syntax.c --- a/src/syntax.c +++ b/src/syntax.c @@ -367,6 +367,9 @@ static reg_extmatch_T *next_match_extmat static win_T *syn_win; /* current window for highlighting */ static buf_T *syn_buf; /* current buffer for highlighting */ static synblock_T *syn_block; /* current buffer for highlighting */ +#ifdef FEAT_RELTIME +static proftime_T *syn_tm; +#endif static linenr_T current_lnum = 0; /* lnum of current state */ static colnr_T current_col = 0; /* column of current state */ static int current_state_stored = 0; /* TRUE if stored current state @@ -494,7 +497,7 @@ static void syn_incl_toplevel(int id, in * window. */ void -syntax_start(win_T *wp, linenr_T lnum) +syntax_start(win_T *wp, linenr_T lnum, proftime_T *syntax_tm UNUSED) { synstate_T *p; synstate_T *last_valid = NULL; @@ -524,6 +527,9 @@ syntax_start(win_T *wp, linenr_T lnum) } changedtick = CHANGEDTICK(syn_buf); syn_win = wp; +#ifdef FEAT_RELTIME + syn_tm = syntax_tm; +#endif /* * Allocate syntax stack when needed. @@ -3295,6 +3301,9 @@ syn_regexec( syn_time_T *st UNUSED) { int r; +#ifdef FEAT_RELTIME + int timed_out = FALSE; +#endif #ifdef FEAT_PROFILE proftime_T pt; @@ -3303,7 +3312,13 @@ syn_regexec( #endif rmp->rmm_maxcol = syn_buf->b_p_smc; - r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, NULL, NULL); + r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, +#ifdef FEAT_RELTIME + syn_tm, &timed_out +#else + NULL, NULL +#endif + ); #ifdef FEAT_PROFILE if (syn_time_on) @@ -3317,6 +3332,10 @@ syn_regexec( ++st->match; } #endif +#ifdef FEAT_RELTIME + if (timed_out) + syn_win->w_s->b_syn_slow = TRUE; +#endif if (r > 0) { @@ -3575,6 +3594,9 @@ syntax_clear(synblock_T *block) int i; block->b_syn_error = FALSE; /* clear previous error */ +#ifdef FEAT_RELTIME + block->b_syn_slow = FALSE; /* clear previous timeout */ +#endif block->b_syn_ic = FALSE; /* Use case, by default */ block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */ block->b_syn_containedin = FALSE; @@ -6542,7 +6564,7 @@ syn_get_id( if (wp->w_buffer != syn_buf || lnum != current_lnum || col < current_col) - syntax_start(wp, lnum); + syntax_start(wp, lnum, NULL); else if (wp->w_buffer == syn_buf && lnum == current_lnum && col > current_col) @@ -6611,9 +6633,14 @@ syn_get_foldlevel(win_T *wp, long lnum) int i; /* Return quickly when there are no fold items at all. */ - if (wp->w_s->b_syn_folditems != 0) - { - syntax_start(wp, lnum); + if (wp->w_s->b_syn_folditems != 0 + && !wp->w_s->b_syn_error +# ifdef SYN_TIME_LIMIT + && !wp->w_s->b_syn_slow +# endif + ) + { + syntax_start(wp, lnum, NULL); for (i = 0; i < current_state.ga_len; ++i) if (CUR_STATE(i).si_flags & HL_FOLD) diff --git a/src/testdir/test_syntax.vim b/src/testdir/test_syntax.vim --- a/src/testdir/test_syntax.vim +++ b/src/testdir/test_syntax.vim @@ -424,3 +424,37 @@ func Test_bg_detection() hi Normal ctermbg=12 call assert_equal('dark', &bg) endfunc + +func Test_syntax_hangs() + if !has('reltime') || !has('float') || !has('syntax') + return + endif + + " This pattern takes a long time to match, it should timeout. + new + call setline(1, ['aaa', repeat('abc ', 1000), 'ccc']) + let start = reltime() + set nolazyredraw redrawtime=101 + syn match Error /\%#=1a*.*X\@<=b*/ + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed > 0.1) + call assert_true(elapsed < 1.0) + + " second time syntax HL is disabled + let start = reltime() + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed < 0.1) + + " after CTRL-L the timeout flag is reset + let start = reltime() + exe "normal \" + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed > 0.1) + call assert_true(elapsed < 1.0) + + set redrawtime& + bwipe! +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -765,6 +765,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 647, +/**/ 646, /**/ 645,