Patch 8.0.0849
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.
Files:      src/terminal.c, src/testdir/test_terminal.vim, src/channel.c


*** ../vim-8.0.0848/src/terminal.c      2017-08-03 14:49:22.618906222 +0200
--- src/terminal.c      2017-08-03 17:01:23.459854636 +0200
***************
*** 40,45 ****
--- 40,46 ----
   * - MS-Windows: no redraw for 'updatetime'  #1915
   * - in bash mouse clicks are inserting characters.
   * - mouse scroll: when over other window, scroll that window.
+  * - add argument to term_wait() for waiting time.
   * - For the scrollback buffer store lines in the buffer, only attributes in
   *   tl_scrollback.
   * - When the job ends:
***************
*** 146,152 ****
  /*
   * Functions with separate implementation for MS-Windows and Unix-like 
systems.
   */
! static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd);
  static void term_report_winsize(term_T *term, int rows, int cols);
  static void term_free_vterm(term_T *term);
  
--- 147,153 ----
  /*
   * Functions with separate implementation for MS-Windows and Unix-like 
systems.
   */
! static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, 
jobopt_T *opt);
  static void term_report_winsize(term_T *term, int rows, int cols);
  static void term_free_vterm(term_T *term);
  
***************
*** 185,199 ****
  }
  
  /*
!  * ":terminal": open a terminal window and execute a job in it.
   */
!     void
! ex_terminal(exarg_T *eap)
  {
      exarg_T   split_ea;
      win_T     *old_curwin = curwin;
      term_T    *term;
-     char_u    *cmd = eap->arg;
  
      if (check_restricted() || check_secure())
        return;
--- 186,234 ----
  }
  
  /*
!  * Initialize job options for a terminal job.
!  * Caller may overrule some of them.
   */
!     static void
! init_job_options(jobopt_T *opt)
! {
!     clear_job_options(opt);
! 
!     opt->jo_mode = MODE_RAW;
!     opt->jo_out_mode = MODE_RAW;
!     opt->jo_err_mode = MODE_RAW;
!     opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
! 
!     opt->jo_io[PART_OUT] = JIO_BUFFER;
!     opt->jo_io[PART_ERR] = JIO_BUFFER;
!     opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
! 
!     opt->jo_modifiable[PART_OUT] = 0;
!     opt->jo_modifiable[PART_ERR] = 0;
!     opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
! 
!     opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
! }
! 
! /*
!  * Set job options mandatory for a terminal job.
!  */
!     static void
! setup_job_options(jobopt_T *opt, int rows, int cols)
! {
!     opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
!     opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
!     opt->jo_pty = TRUE;
!     opt->jo_term_rows = rows;
!     opt->jo_term_cols = cols;
! }
! 
!     static void
! term_start(char_u *cmd, jobopt_T *opt)
  {
      exarg_T   split_ea;
      win_T     *old_curwin = curwin;
      term_T    *term;
  
      if (check_restricted() || check_secure())
        return;
***************
*** 256,264 ****
                                  (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
  
      set_term_and_win_size(term);
  
      /* System dependent: setup the vterm and start the job in it. */
!     if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd) == OK)
      {
        /* store the size we ended up with */
        vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
--- 291,300 ----
                                  (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
  
      set_term_and_win_size(term);
+     setup_job_options(opt, term->tl_rows, term->tl_cols);
  
      /* System dependent: setup the vterm and start the job in it. */
!     if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd, opt) == OK)
      {
        /* store the size we ended up with */
        vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
***************
*** 274,279 ****
--- 310,329 ----
  }
  
  /*
+  * ":terminal": open a terminal window and execute a job in it.
+  */
+     void
+ ex_terminal(exarg_T *eap)
+ {
+     jobopt_T opt;
+ 
+     init_job_options(&opt);
+     /* TODO: get options from before the command */
+ 
+     term_start(eap->arg, &opt);
+ }
+ 
+ /*
   * Free the scrollback buffer for "term".
   */
      static void
***************
*** 965,972 ****
        update_cursor(curbuf->b_term, FALSE);
  
        c = term_vgetc();
!       if (curbuf->b_term->tl_vterm == NULL
!                                         || !term_job_running(curbuf->b_term))
            /* job finished while waiting for a character */
            break;
  
--- 1015,1021 ----
        update_cursor(curbuf->b_term, FALSE);
  
        c = term_vgetc();
!       if (!term_use_loop())
            /* job finished while waiting for a character */
            break;
  
***************
*** 993,1000 ****
  #ifdef FEAT_CMDL_INFO
            clear_showcmd();
  #endif
!           if (curbuf->b_term->tl_vterm == NULL
!                                         || !term_job_running(curbuf->b_term))
                /* job finished while waiting for a character */
                break;
  
--- 1042,1048 ----
  #ifdef FEAT_CMDL_INFO
            clear_showcmd();
  #endif
!           if (!term_use_loop())
                /* job finished while waiting for a character */
                break;
  
***************
*** 1614,1648 ****
  }
  
  /*
-  * Set job options common for Unix and MS-Windows.
-  */
-     static void
- setup_job_options(jobopt_T *opt, int rows, int cols)
- {
-     clear_job_options(opt);
-     opt->jo_mode = MODE_RAW;
-     opt->jo_out_mode = MODE_RAW;
-     opt->jo_err_mode = MODE_RAW;
-     opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
- 
-     opt->jo_io[PART_OUT] = JIO_BUFFER;
-     opt->jo_io[PART_ERR] = JIO_BUFFER;
-     opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
- 
-     opt->jo_modifiable[PART_OUT] = 0;
-     opt->jo_modifiable[PART_ERR] = 0;
-     opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
- 
-     opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
-     opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
-     opt->jo_pty = TRUE;
-     opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
- 
-     opt->jo_term_rows = rows;
-     opt->jo_term_cols = cols;
- }
- 
- /*
   * Create a new vterm and initialize it.
   */
      static void
--- 1662,1667 ----
***************
*** 2089,2100 ****
  f_term_start(typval_T *argvars, typval_T *rettv)
  {
      char_u    *cmd = get_tv_string_chk(&argvars[0]);
!     exarg_T   ea;
  
      if (cmd == NULL)
        return;
!     ea.arg = cmd;
!     ex_terminal(&ea);
  
      if (curbuf->b_term != NULL)
        rettv->vval.v_number = curbuf->b_fnum;
--- 2108,2126 ----
  f_term_start(typval_T *argvars, typval_T *rettv)
  {
      char_u    *cmd = get_tv_string_chk(&argvars[0]);
!     jobopt_T  opt;
  
      if (cmd == NULL)
        return;
!     init_job_options(&opt);
!     /* TODO: allow more job options */
!     if (argvars[1].v_type != VAR_UNKNOWN
!           && get_job_options(&argvars[1], &opt,
!               JO_TIMEOUT_ALL + JO_STOPONEXIT
!               + JO_EXIT_CB + JO_CLOSE_CALLBACK) == FAIL)
!       return;
! 
!     term_start(cmd, &opt);
  
      if (curbuf->b_term != NULL)
        rettv->vval.v_number = curbuf->b_fnum;
***************
*** 2109,2127 ****
      buf_T     *buf = term_get_buf(argvars);
  
      if (buf == NULL)
        return;
  
      /* Get the job status, this will detect a job that finished. */
!     if (buf->b_term->tl_job != NULL)
!       (void)job_status(buf->b_term->tl_job);
  
!     /* Check for any pending channel I/O. */
!     vpeekc_any();
!     ui_delay(10L, FALSE);
! 
!     /* Flushing messages on channels is hopefully sufficient.
!      * TODO: is there a better way? */
!     parse_queued_messages();
  }
  
  # ifdef WIN3264
--- 2135,2174 ----
      buf_T     *buf = term_get_buf(argvars);
  
      if (buf == NULL)
+     {
+       ch_log(NULL, "term_wait(): invalid argument");
        return;
+     }
  
      /* Get the job status, this will detect a job that finished. */
!     if (buf->b_term->tl_job == NULL
!           || STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
!     {
!       /* The job is dead, keep reading channel I/O until the channel is
!        * closed. */
!       while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
!       {
!           mch_char_avail();
!           parse_queued_messages();
!           ui_delay(10L, FALSE);
!       }
!       mch_char_avail();
!       parse_queued_messages();
!     }
!     else
!     {
!       mch_char_avail();
!       parse_queued_messages();
  
!       /* Wait for 10 msec for any channel I/O. */
!       /* TODO: use delay from optional argument */
!       ui_delay(10L, TRUE);
!       mch_char_avail();
! 
!       /* Flushing messages on channels is hopefully sufficient.
!        * TODO: is there a better way? */
!       parse_queued_messages();
!     }
  }
  
  # ifdef WIN3264
***************
*** 2209,2220 ****
   * Return OK or FAIL.
   */
      static int
! term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
  {
      WCHAR         *p;
      channel_T     *channel = NULL;
      job_T         *job = NULL;
-     jobopt_T      opt;
      DWORD         error;
      HANDLE        jo = NULL, child_process_handle, child_thread_handle;
      void          *winpty_err;
--- 2256,2266 ----
   * Return OK or FAIL.
   */
      static int
! term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T 
*opt)
  {
      WCHAR         *p;
      channel_T     *channel = NULL;
      job_T         *job = NULL;
      DWORD         error;
      HANDLE        jo = NULL, child_process_handle, child_thread_handle;
      void          *winpty_err;
***************
*** 2298,2305 ****
  
      create_vterm(term, rows, cols);
  
!     setup_job_options(&opt, rows, cols);
!     channel_set_job(channel, job, &opt);
  
      job->jv_channel = channel;
      job->jv_proc_info.hProcess = child_process_handle;
--- 2344,2350 ----
  
      create_vterm(term, rows, cols);
  
!     channel_set_job(channel, job, opt);
  
      job->jv_channel = channel;
      job->jv_proc_info.hProcess = child_process_handle;
***************
*** 2381,2398 ****
   * Return OK or FAIL.
   */
      static int
! term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
  {
      typval_T  argvars[2];
-     jobopt_T  opt;
  
      create_vterm(term, rows, cols);
  
      /* TODO: if the command is "NONE" only create a pty. */
      argvars[0].v_type = VAR_STRING;
      argvars[0].vval.v_string = cmd;
!     setup_job_options(&opt, rows, cols);
!     term->tl_job = job_start(argvars, &opt);
      if (term->tl_job != NULL)
        ++term->tl_job->jv_refcount;
  
--- 2426,2442 ----
   * Return OK or FAIL.
   */
      static int
! term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T 
*opt)
  {
      typval_T  argvars[2];
  
      create_vterm(term, rows, cols);
  
      /* TODO: if the command is "NONE" only create a pty. */
      argvars[0].v_type = VAR_STRING;
      argvars[0].vval.v_string = cmd;
! 
!     term->tl_job = job_start(argvars, opt);
      if (term->tl_job != NULL)
        ++term->tl_job->jv_refcount;
  
*** ../vim-8.0.0848/src/testdir/test_terminal.vim       2017-08-03 
13:51:02.388784785 +0200
--- src/testdir/test_terminal.vim       2017-08-03 16:36:16.435060718 +0200
***************
*** 86,91 ****
--- 86,108 ----
    unlet g:job
  endfunc
  
+ func! s:Nasty_exit_cb(job, st)
+   exe g:buf . 'bwipe!'
+   let g:buf = 0
+ endfunc
+ 
+ func Test_terminal_nasty_cb()
+   let cmd = Get_cat_cmd()
+   let g:buf = term_start(cmd, {'exit_cb': function('s:Nasty_exit_cb')})
+   let g:job = term_getjob(g:buf)
+ 
+   call WaitFor('job_status(g:job) == "dead"')
+   call WaitFor('g:buf == 0')
+   unlet g:buf
+   unlet g:job
+   call delete('Xtext')
+ endfunc
+ 
  func Check_123(buf)
    let l = term_scrape(a:buf, 0)
    call assert_true(len(l) == 0)
***************
*** 113,125 ****
    call assert_equal('123', l)
  endfunc
  
! func Test_terminal_scrape()
    if has('win32')
!     let cmd = 'cmd /c "cls && color 2 && echo 123"'
    else
      call writefile(["\<Esc>[32m123"], 'Xtext')
!     let cmd = "cat Xtext"
    endif
    let buf = term_start(cmd)
  
    let termlist = term_list()
--- 130,146 ----
    call assert_equal('123', l)
  endfunc
  
! func Get_cat_cmd()
    if has('win32')
!     return 'cmd /c "cls && color 2 && echo 123"'
    else
      call writefile(["\<Esc>[32m123"], 'Xtext')
!     return "cat Xtext"
    endif
+ endfunc
+ 
+ func Test_terminal_scrape()
+   let cmd = Get_cat_cmd()
    let buf = term_start(cmd)
  
    let termlist = term_list()
*** ../vim-8.0.0848/src/channel.c       2017-08-03 14:49:22.614906252 +0200
--- src/channel.c       2017-08-03 15:49:26.096113512 +0200
***************
*** 4160,4166 ****
      hashitem_T        *hi;
      ch_part_T part;
  
-     opt->jo_set = 0;
      if (tv->v_type == VAR_UNKNOWN)
        return OK;
      if (tv->v_type != VAR_DICT)
--- 4160,4165 ----
***************
*** 4616,4621 ****
--- 4615,4621 ----
        int             dummy;
  
        /* Invoke the exit callback. Make sure the refcount is > 0. */
+       ch_log(job->jv_channel, "Invoking exit callback %s", job->jv_exit_cb);
        ++job->jv_refcount;
        argv[0].v_type = VAR_JOB;
        argv[0].vval.v_job = job;
***************
*** 4888,4894 ****
        if (get_job_options(&argvars[1], &opt,
            JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + JO_STOPONEXIT
                           + JO_EXIT_CB + JO_OUT_IO + JO_BLOCK_WRITE) == FAIL)
!       goto theend;
      }
  
      /* Check that when io is "file" that there is a file name. */
--- 4888,4894 ----
        if (get_job_options(&argvars[1], &opt,
            JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + JO_STOPONEXIT
                           + JO_EXIT_CB + JO_OUT_IO + JO_BLOCK_WRITE) == FAIL)
!           goto theend;
      }
  
      /* Check that when io is "file" that there is a file name. */
*** ../vim-8.0.0848/src/version.c       2017-08-03 14:49:22.618906222 +0200
--- src/version.c       2017-08-03 14:57:41.315216159 +0200
***************
*** 771,772 ****
--- 771,774 ----
  {   /* Add new patch number below this line */
+ /**/
+     849,
  /**/

-- 
Courtroom Quote #19:
Q:  Doctor, how many autopsies have you performed on dead people?
A:  All my autopsies have been performed on dead people.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Raspunde prin e-mail lui