Skip to content

Commit 3c3a80d

Browse files
committed
patch 8.0.0849: crash when job exit callback wipes the terminal
Problem: Crash when job exit callback wipes the terminal. Solution: Check for b_term to be NULL. (Yasuhiro Matsumoto, closes #1922) Implement options for term_start() to be able to test. Make term_wait() more reliable.
1 parent 2f3a90a commit 3c3a80d

4 files changed

Lines changed: 130 additions & 63 deletions

File tree

src/channel.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4160,7 +4160,6 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported)
41604160
hashitem_T *hi;
41614161
ch_part_T part;
41624162

4163-
opt->jo_set = 0;
41644163
if (tv->v_type == VAR_UNKNOWN)
41654164
return OK;
41664165
if (tv->v_type != VAR_DICT)
@@ -4616,6 +4615,7 @@ job_cleanup(job_T *job)
46164615
int dummy;
46174616

46184617
/* Invoke the exit callback. Make sure the refcount is > 0. */
4618+
ch_log(job->jv_channel, "Invoking exit callback %s", job->jv_exit_cb);
46194619
++job->jv_refcount;
46204620
argv[0].v_type = VAR_JOB;
46214621
argv[0].vval.v_job = job;
@@ -4888,7 +4888,7 @@ job_start(typval_T *argvars, jobopt_T *opt_arg)
48884888
if (get_job_options(&argvars[1], &opt,
48894889
JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + JO_STOPONEXIT
48904890
+ JO_EXIT_CB + JO_OUT_IO + JO_BLOCK_WRITE) == FAIL)
4891-
goto theend;
4891+
goto theend;
48924892
}
48934893

48944894
/* Check that when io is "file" that there is a file name. */

src/terminal.c

Lines changed: 102 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* - MS-Windows: no redraw for 'updatetime' #1915
4141
* - in bash mouse clicks are inserting characters.
4242
* - mouse scroll: when over other window, scroll that window.
43+
* - add argument to term_wait() for waiting time.
4344
* - For the scrollback buffer store lines in the buffer, only attributes in
4445
* tl_scrollback.
4546
* - When the job ends:
@@ -146,7 +147,7 @@ static term_T *first_term = NULL;
146147
/*
147148
* Functions with separate implementation for MS-Windows and Unix-like systems.
148149
*/
149-
static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd);
150+
static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt);
150151
static void term_report_winsize(term_T *term, int rows, int cols);
151152
static void term_free_vterm(term_T *term);
152153

@@ -185,15 +186,49 @@ set_term_and_win_size(term_T *term)
185186
}
186187

187188
/*
188-
* ":terminal": open a terminal window and execute a job in it.
189+
* Initialize job options for a terminal job.
190+
* Caller may overrule some of them.
189191
*/
190-
void
191-
ex_terminal(exarg_T *eap)
192+
static void
193+
init_job_options(jobopt_T *opt)
194+
{
195+
clear_job_options(opt);
196+
197+
opt->jo_mode = MODE_RAW;
198+
opt->jo_out_mode = MODE_RAW;
199+
opt->jo_err_mode = MODE_RAW;
200+
opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
201+
202+
opt->jo_io[PART_OUT] = JIO_BUFFER;
203+
opt->jo_io[PART_ERR] = JIO_BUFFER;
204+
opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
205+
206+
opt->jo_modifiable[PART_OUT] = 0;
207+
opt->jo_modifiable[PART_ERR] = 0;
208+
opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
209+
210+
opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
211+
}
212+
213+
/*
214+
* Set job options mandatory for a terminal job.
215+
*/
216+
static void
217+
setup_job_options(jobopt_T *opt, int rows, int cols)
218+
{
219+
opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
220+
opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
221+
opt->jo_pty = TRUE;
222+
opt->jo_term_rows = rows;
223+
opt->jo_term_cols = cols;
224+
}
225+
226+
static void
227+
term_start(char_u *cmd, jobopt_T *opt)
192228
{
193229
exarg_T split_ea;
194230
win_T *old_curwin = curwin;
195231
term_T *term;
196-
char_u *cmd = eap->arg;
197232

198233
if (check_restricted() || check_secure())
199234
return;
@@ -256,9 +291,10 @@ ex_terminal(exarg_T *eap)
256291
(char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
257292

258293
set_term_and_win_size(term);
294+
setup_job_options(opt, term->tl_rows, term->tl_cols);
259295

260296
/* System dependent: setup the vterm and start the job in it. */
261-
if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd) == OK)
297+
if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd, opt) == OK)
262298
{
263299
/* store the size we ended up with */
264300
vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
@@ -273,6 +309,20 @@ ex_terminal(exarg_T *eap)
273309
}
274310
}
275311

312+
/*
313+
* ":terminal": open a terminal window and execute a job in it.
314+
*/
315+
void
316+
ex_terminal(exarg_T *eap)
317+
{
318+
jobopt_T opt;
319+
320+
init_job_options(&opt);
321+
/* TODO: get options from before the command */
322+
323+
term_start(eap->arg, &opt);
324+
}
325+
276326
/*
277327
* Free the scrollback buffer for "term".
278328
*/
@@ -965,8 +1015,7 @@ terminal_loop(void)
9651015
update_cursor(curbuf->b_term, FALSE);
9661016

9671017
c = term_vgetc();
968-
if (curbuf->b_term->tl_vterm == NULL
969-
|| !term_job_running(curbuf->b_term))
1018+
if (!term_use_loop())
9701019
/* job finished while waiting for a character */
9711020
break;
9721021

@@ -993,8 +1042,7 @@ terminal_loop(void)
9931042
#ifdef FEAT_CMDL_INFO
9941043
clear_showcmd();
9951044
#endif
996-
if (curbuf->b_term->tl_vterm == NULL
997-
|| !term_job_running(curbuf->b_term))
1045+
if (!term_use_loop())
9981046
/* job finished while waiting for a character */
9991047
break;
10001048

@@ -1613,35 +1661,6 @@ term_get_attr(buf_T *buf, linenr_T lnum, int col)
16131661
return cell2attr(line->sb_cells + col);
16141662
}
16151663

1616-
/*
1617-
* Set job options common for Unix and MS-Windows.
1618-
*/
1619-
static void
1620-
setup_job_options(jobopt_T *opt, int rows, int cols)
1621-
{
1622-
clear_job_options(opt);
1623-
opt->jo_mode = MODE_RAW;
1624-
opt->jo_out_mode = MODE_RAW;
1625-
opt->jo_err_mode = MODE_RAW;
1626-
opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
1627-
1628-
opt->jo_io[PART_OUT] = JIO_BUFFER;
1629-
opt->jo_io[PART_ERR] = JIO_BUFFER;
1630-
opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
1631-
1632-
opt->jo_modifiable[PART_OUT] = 0;
1633-
opt->jo_modifiable[PART_ERR] = 0;
1634-
opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
1635-
1636-
opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
1637-
opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
1638-
opt->jo_pty = TRUE;
1639-
opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
1640-
1641-
opt->jo_term_rows = rows;
1642-
opt->jo_term_cols = cols;
1643-
}
1644-
16451664
/*
16461665
* Create a new vterm and initialize it.
16471666
*/
@@ -2089,12 +2108,19 @@ f_term_sendkeys(typval_T *argvars, typval_T *rettv)
20892108
f_term_start(typval_T *argvars, typval_T *rettv)
20902109
{
20912110
char_u *cmd = get_tv_string_chk(&argvars[0]);
2092-
exarg_T ea;
2111+
jobopt_T opt;
20932112

20942113
if (cmd == NULL)
20952114
return;
2096-
ea.arg = cmd;
2097-
ex_terminal(&ea);
2115+
init_job_options(&opt);
2116+
/* TODO: allow more job options */
2117+
if (argvars[1].v_type != VAR_UNKNOWN
2118+
&& get_job_options(&argvars[1], &opt,
2119+
JO_TIMEOUT_ALL + JO_STOPONEXIT
2120+
+ JO_EXIT_CB + JO_CLOSE_CALLBACK) == FAIL)
2121+
return;
2122+
2123+
term_start(cmd, &opt);
20982124

20992125
if (curbuf->b_term != NULL)
21002126
rettv->vval.v_number = curbuf->b_fnum;
@@ -2109,19 +2135,40 @@ f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
21092135
buf_T *buf = term_get_buf(argvars);
21102136

21112137
if (buf == NULL)
2138+
{
2139+
ch_log(NULL, "term_wait(): invalid argument");
21122140
return;
2141+
}
21132142

21142143
/* Get the job status, this will detect a job that finished. */
2115-
if (buf->b_term->tl_job != NULL)
2116-
(void)job_status(buf->b_term->tl_job);
2144+
if (buf->b_term->tl_job == NULL
2145+
|| STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
2146+
{
2147+
/* The job is dead, keep reading channel I/O until the channel is
2148+
* closed. */
2149+
while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
2150+
{
2151+
mch_char_avail();
2152+
parse_queued_messages();
2153+
ui_delay(10L, FALSE);
2154+
}
2155+
mch_char_avail();
2156+
parse_queued_messages();
2157+
}
2158+
else
2159+
{
2160+
mch_char_avail();
2161+
parse_queued_messages();
21172162

2118-
/* Check for any pending channel I/O. */
2119-
vpeekc_any();
2120-
ui_delay(10L, FALSE);
2163+
/* Wait for 10 msec for any channel I/O. */
2164+
/* TODO: use delay from optional argument */
2165+
ui_delay(10L, TRUE);
2166+
mch_char_avail();
21212167

2122-
/* Flushing messages on channels is hopefully sufficient.
2123-
* TODO: is there a better way? */
2124-
parse_queued_messages();
2168+
/* Flushing messages on channels is hopefully sufficient.
2169+
* TODO: is there a better way? */
2170+
parse_queued_messages();
2171+
}
21252172
}
21262173

21272174
# ifdef WIN3264
@@ -2209,12 +2256,11 @@ dyn_winpty_init(void)
22092256
* Return OK or FAIL.
22102257
*/
22112258
static int
2212-
term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
2259+
term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt)
22132260
{
22142261
WCHAR *p;
22152262
channel_T *channel = NULL;
22162263
job_T *job = NULL;
2217-
jobopt_T opt;
22182264
DWORD error;
22192265
HANDLE jo = NULL, child_process_handle, child_thread_handle;
22202266
void *winpty_err;
@@ -2298,8 +2344,7 @@ term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
22982344

22992345
create_vterm(term, rows, cols);
23002346

2301-
setup_job_options(&opt, rows, cols);
2302-
channel_set_job(channel, job, &opt);
2347+
channel_set_job(channel, job, opt);
23032348

23042349
job->jv_channel = channel;
23052350
job->jv_proc_info.hProcess = child_process_handle;
@@ -2381,18 +2426,17 @@ term_report_winsize(term_T *term, int rows, int cols)
23812426
* Return OK or FAIL.
23822427
*/
23832428
static int
2384-
term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
2429+
term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt)
23852430
{
23862431
typval_T argvars[2];
2387-
jobopt_T opt;
23882432

23892433
create_vterm(term, rows, cols);
23902434

23912435
/* TODO: if the command is "NONE" only create a pty. */
23922436
argvars[0].v_type = VAR_STRING;
23932437
argvars[0].vval.v_string = cmd;
2394-
setup_job_options(&opt, rows, cols);
2395-
term->tl_job = job_start(argvars, &opt);
2438+
2439+
term->tl_job = job_start(argvars, opt);
23962440
if (term->tl_job != NULL)
23972441
++term->tl_job->jv_refcount;
23982442

src/testdir/test_terminal.vim

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ func Test_terminal_hide_buffer()
8686
unlet g:job
8787
endfunc
8888

89+
func! s:Nasty_exit_cb(job, st)
90+
exe g:buf . 'bwipe!'
91+
let g:buf = 0
92+
endfunc
93+
94+
func Test_terminal_nasty_cb()
95+
let cmd = Get_cat_cmd()
96+
let g:buf = term_start(cmd, {'exit_cb': function('s:Nasty_exit_cb')})
97+
let g:job = term_getjob(g:buf)
98+
99+
call WaitFor('job_status(g:job) == "dead"')
100+
call WaitFor('g:buf == 0')
101+
unlet g:buf
102+
unlet g:job
103+
call delete('Xtext')
104+
endfunc
105+
89106
func Check_123(buf)
90107
let l = term_scrape(a:buf, 0)
91108
call assert_true(len(l) == 0)
@@ -113,13 +130,17 @@ func Check_123(buf)
113130
call assert_equal('123', l)
114131
endfunc
115132

116-
func Test_terminal_scrape()
133+
func Get_cat_cmd()
117134
if has('win32')
118-
let cmd = 'cmd /c "cls && color 2 && echo 123"'
135+
return 'cmd /c "cls && color 2 && echo 123"'
119136
else
120137
call writefile(["\<Esc>[32m123"], 'Xtext')
121-
let cmd = "cat Xtext"
138+
return "cat Xtext"
122139
endif
140+
endfunc
141+
142+
func Test_terminal_scrape()
143+
let cmd = Get_cat_cmd()
123144
let buf = term_start(cmd)
124145

125146
let termlist = term_list()

src/version.c

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

770770
static int included_patches[] =
771771
{ /* Add new patch number below this line */
772+
/**/
773+
849,
772774
/**/
773775
848,
774776
/**/

0 commit comments

Comments
 (0)