Patch 7.4.1727
Problem:    Cannot detect a crash in tests when caused by garbagecollect().
Solution:   Add garbagecollect_for_testing().  Do not free a job if is still
            useful.
Files:      src/channel.c, src/eval.c, src/getchar.c, src/main.c, src/vim.h,
            src/proto/eval.pro, src/testdir/runtest.vim,
            src/testdir/test_channel.vim, runtime/doc/eval.txt


*** ../vim-7.4.1726/src/channel.c       2016-04-08 17:07:09.542160709 +0200
--- src/channel.c       2016-04-14 12:37:05.582716400 +0200
***************
*** 458,465 ****
        ch_next = ch->ch_next;
        if ((ch->ch_copyID & mask) != (copyID & mask))
        {
!           /* Free the channel and ordinary items it contains, but don't
!            * recurse into Lists, Dictionaries etc. */
            channel_free_channel(ch);
        }
      }
--- 458,464 ----
        ch_next = ch->ch_next;
        if ((ch->ch_copyID & mask) != (copyID & mask))
        {
!           /* Free the channel struct itself. */
            channel_free_channel(ch);
        }
      }
***************
*** 4006,4011 ****
--- 4005,4021 ----
      }
  }
  
+ /*
+  * Return TRUE if the job should not be freed yet.  Do not free the job when
+  * it has not ended yet and there is a "stoponexit" flag or an exit callback.
+  */
+     static int
+ job_still_useful(job_T *job)
+ {
+     return job->jv_status == JOB_STARTED
+                  && (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL);
+ }
+ 
      void
  job_unref(job_T *job)
  {
***************
*** 4013,4020 ****
      {
        /* Do not free the job when it has not ended yet and there is a
         * "stoponexit" flag or an exit callback. */
!       if (job->jv_status != JOB_STARTED
!               || (job->jv_stoponexit == NULL && job->jv_exit_cb == NULL))
        {
            job_free(job);
        }
--- 4023,4029 ----
      {
        /* Do not free the job when it has not ended yet and there is a
         * "stoponexit" flag or an exit callback. */
!       if (!job_still_useful(job))
        {
            job_free(job);
        }
***************
*** 4036,4042 ****
      job_T     *job;
  
      for (job = first_job; job != NULL; job = job->jv_next)
!       if ((job->jv_copyID & mask) != (copyID & mask))
        {
            /* Free the channel and ordinary items it contains, but don't
             * recurse into Lists, Dictionaries etc. */
--- 4045,4052 ----
      job_T     *job;
  
      for (job = first_job; job != NULL; job = job->jv_next)
!       if ((job->jv_copyID & mask) != (copyID & mask)
!                                                   && !job_still_useful(job))
        {
            /* Free the channel and ordinary items it contains, but don't
             * recurse into Lists, Dictionaries etc. */
***************
*** 4055,4064 ****
      for (job = first_job; job != NULL; job = job_next)
      {
        job_next = job->jv_next;
!       if ((job->jv_copyID & mask) != (copyID & mask))
        {
!           /* Free the channel and ordinary items it contains, but don't
!            * recurse into Lists, Dictionaries etc. */
            job_free_job(job);
        }
      }
--- 4065,4074 ----
      for (job = first_job; job != NULL; job = job_next)
      {
        job_next = job->jv_next;
!       if ((job->jv_copyID & mask) != (copyID & mask)
!                                                   && !job_still_useful(job))
        {
!           /* Free the job struct itself. */
            job_free_job(job);
        }
      }
*** ../vim-7.4.1726/src/eval.c  2016-04-12 22:18:47.670129251 +0200
--- src/eval.c  2016-04-14 12:43:01.882944041 +0200
***************
*** 373,378 ****
--- 373,379 ----
      {VV_NAME("null",           VAR_SPECIAL), VV_RO},
      {VV_NAME("none",           VAR_SPECIAL), VV_RO},
      {VV_NAME("vim_did_enter",  VAR_NUMBER), VV_RO},
+     {VV_NAME("testing",                VAR_NUMBER), 0},
  };
  
  /* shorthand */
***************
*** 580,585 ****
--- 581,587 ----
  static void f_foreground(typval_T *argvars, typval_T *rettv);
  static void f_function(typval_T *argvars, typval_T *rettv);
  static void f_garbagecollect(typval_T *argvars, typval_T *rettv);
+ static void f_garbagecollect_for_testing(typval_T *argvars, typval_T *rettv);
  static void f_get(typval_T *argvars, typval_T *rettv);
  static void f_getbufline(typval_T *argvars, typval_T *rettv);
  static void f_getbufvar(typval_T *argvars, typval_T *rettv);
***************
*** 1029,1035 ****
      ga_clear(&ga_scripts);
  
      /* unreferenced lists and dicts */
!     (void)garbage_collect();
  
      /* functions */
      free_all_functions();
--- 1031,1037 ----
      ga_clear(&ga_scripts);
  
      /* unreferenced lists and dicts */
!     (void)garbage_collect(FALSE);
  
      /* functions */
      free_all_functions();
***************
*** 6889,6894 ****
--- 6891,6899 ----
      return current_copyID;
  }
  
+ /* Used by get_func_tv() */
+ static garray_T funcargs = GA_EMPTY;
+ 
  /*
   * Garbage collection for lists and dictionaries.
   *
***************
*** 6911,6920 ****
  
  /*
   * Do garbage collection for lists and dicts.
   * Return TRUE if some memory was freed.
   */
      int
! garbage_collect(void)
  {
      int               copyID;
      int               abort = FALSE;
--- 6916,6926 ----
  
  /*
   * Do garbage collection for lists and dicts.
+  * When "testing" is TRUE this is called from garbagecollect_for_testing().
   * Return TRUE if some memory was freed.
   */
      int
! garbage_collect(int testing)
  {
      int               copyID;
      int               abort = FALSE;
***************
*** 6928,6937 ****
      tabpage_T *tp;
  #endif
  
!     /* Only do this once. */
!     want_garbage_collect = FALSE;
!     may_garbage_collect = FALSE;
!     garbage_collect_at_exit = FALSE;
  
      /* We advance by two because we add one for items referenced through
       * previous_funccal. */
--- 6934,6946 ----
      tabpage_T *tp;
  #endif
  
!     if (!testing)
!     {
!       /* Only do this once. */
!       want_garbage_collect = FALSE;
!       may_garbage_collect = FALSE;
!       garbage_collect_at_exit = FALSE;
!     }
  
      /* We advance by two because we add one for items referenced through
       * previous_funccal. */
***************
*** 6989,6994 ****
--- 6998,7008 ----
        abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
      }
  
+     /* function call arguments, if v:testing is set. */
+     for (i = 0; i < funcargs.ga_len; ++i)
+       abort = abort || set_ref_in_item(((typval_T **)funcargs.ga_data)[i],
+                                                         copyID, NULL, NULL);
+ 
      /* v: vars */
      abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
  
***************
*** 7034,7040 ****
        if (did_free_funccal)
            /* When a funccal was freed some more items might be garbage
             * collected, so run again. */
!           (void)garbage_collect();
      }
      else if (p_verbose > 0)
      {
--- 7048,7054 ----
        if (did_free_funccal)
            /* When a funccal was freed some more items might be garbage
             * collected, so run again. */
!           (void)garbage_collect(testing);
      }
      else if (p_verbose > 0)
      {
***************
*** 8424,8429 ****
--- 8438,8444 ----
      {"foreground",    0, 0, f_foreground},
      {"function",      1, 3, f_function},
      {"garbagecollect",        0, 1, f_garbagecollect},
+     {"garbagecollect_for_testing",    0, 0, f_garbagecollect_for_testing},
      {"get",           2, 3, f_get},
      {"getbufline",    2, 3, f_getbufline},
      {"getbufvar",     2, 3, f_getbufvar},
***************
*** 8896,8903 ****
--- 8911,8936 ----
        ret = FAIL;
  
      if (ret == OK)
+     {
+       int             i = 0;
+ 
+       if (get_vim_var_nr(VV_TESTING))
+       {
+           /* Prepare for calling garbagecollect_for_testing(), need to know
+            * what variables are used on the call stack. */
+           if (funcargs.ga_itemsize == 0)
+               ga_init2(&funcargs, (int)sizeof(typval_T *), 50);
+           for (i = 0; i < argcount; ++i)
+               if (ga_grow(&funcargs, 1) == OK)
+                   ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] =
+                                                                 &argvars[i];
+       }
+ 
        ret = call_func(name, len, rettv, argcount, argvars,
                 firstline, lastline, doesrange, evaluate, partial, selfdict);
+ 
+       funcargs.ga_len -= i;
+     }
      else if (!aborting())
      {
        if (argcount == MAX_FUNC_ARGS)
***************
*** 12318,12323 ****
--- 12351,12367 ----
  }
  
  /*
+  * "garbagecollect_for_testing()" function
+  */
+     static void
+ f_garbagecollect_for_testing(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
+ {
+     /* This is dangerous, any Lists and Dicts used internally may be freed
+      * while still in use. */
+     garbage_collect(TRUE);
+ }
+ 
+ /*
   * "get()" function
   */
      static void
*** ../vim-7.4.1726/src/getchar.c       2016-02-23 14:52:31.881232212 +0100
--- src/getchar.c       2016-04-14 11:00:48.555441735 +0200
***************
*** 1523,1529 ****
      updatescript(0);
  #ifdef FEAT_EVAL
      if (may_garbage_collect)
!       garbage_collect();
  #endif
  }
  
--- 1523,1529 ----
      updatescript(0);
  #ifdef FEAT_EVAL
      if (may_garbage_collect)
!       garbage_collect(FALSE);
  #endif
  }
  
***************
*** 1571,1577 ****
      /* Do garbage collection when garbagecollect() was called previously and
       * we are now at the toplevel. */
      if (may_garbage_collect && want_garbage_collect)
!       garbage_collect();
  #endif
  
      /*
--- 1571,1577 ----
      /* Do garbage collection when garbagecollect() was called previously and
       * we are now at the toplevel. */
      if (may_garbage_collect && want_garbage_collect)
!       garbage_collect(FALSE);
  #endif
  
      /*
*** ../vim-7.4.1726/src/main.c  2016-04-06 23:06:18.586052503 +0200
--- src/main.c  2016-04-14 11:00:58.435338807 +0200
***************
*** 1531,1537 ****
  #endif
  #ifdef FEAT_EVAL
      if (garbage_collect_at_exit)
!       garbage_collect();
  #endif
  #if defined(WIN32) && defined(FEAT_MBYTE)
      free_cmd_argsW();
--- 1531,1537 ----
  #endif
  #ifdef FEAT_EVAL
      if (garbage_collect_at_exit)
!       garbage_collect(FALSE);
  #endif
  #if defined(WIN32) && defined(FEAT_MBYTE)
      free_cmd_argsW();
*** ../vim-7.4.1726/src/vim.h   2016-03-28 19:16:15.669846492 +0200
--- src/vim.h   2016-04-14 11:18:58.496104039 +0200
***************
*** 1868,1874 ****
  #define VV_NULL               65
  #define VV_NONE               66
  #define VV_VIM_DID_ENTER 67
! #define VV_LEN                68      /* number of v: vars */
  
  /* used for v_number in VAR_SPECIAL */
  #define VVAL_FALSE    0L
--- 1868,1875 ----
  #define VV_NULL               65
  #define VV_NONE               66
  #define VV_VIM_DID_ENTER 67
! #define VV_TESTING    68
! #define VV_LEN                69      /* number of v: vars */
  
  /* used for v_number in VAR_SPECIAL */
  #define VVAL_FALSE    0L
*** ../vim-7.4.1726/src/proto/eval.pro  2016-04-08 17:07:09.546160667 +0200
--- src/proto/eval.pro  2016-04-14 11:01:12.883188293 +0200
***************
*** 49,55 ****
  list_T *list_alloc(void);
  int rettv_list_alloc(typval_T *rettv);
  void list_unref(list_T *l);
- void list_free_internal(list_T *l);
  void list_free(list_T *l);
  listitem_T *listitem_alloc(void);
  void listitem_free(listitem_T *item);
--- 49,54 ----
***************
*** 66,79 ****
  void list_insert(list_T *l, listitem_T *ni, listitem_T *item);
  void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2);
  int get_copyID(void);
! int garbage_collect(void);
  int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack);
  int set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack);
  int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, 
list_stack_T **list_stack);
  dict_T *dict_alloc(void);
  int rettv_dict_alloc(typval_T *rettv);
  void dict_unref(dict_T *d);
- void dict_free_internal(dict_T *d);
  void dict_free(dict_T *d);
  dictitem_T *dictitem_alloc(char_u *key);
  void dictitem_free(dictitem_T *item);
--- 65,77 ----
  void list_insert(list_T *l, listitem_T *ni, listitem_T *item);
  void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2);
  int get_copyID(void);
! int garbage_collect(int testing);
  int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack);
  int set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack);
  int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, 
list_stack_T **list_stack);
  dict_T *dict_alloc(void);
  int rettv_dict_alloc(typval_T *rettv);
  void dict_unref(dict_T *d);
  void dict_free(dict_T *d);
  dictitem_T *dictitem_alloc(char_u *key);
  void dictitem_free(dictitem_T *item);
*** ../vim-7.4.1726/src/testdir/runtest.vim     2016-03-30 20:50:41.905696041 
+0200
--- src/testdir/runtest.vim     2016-04-14 11:17:30.929013730 +0200
***************
*** 60,65 ****
--- 60,68 ----
  
  let s:srcdir = expand('%:p:h:h')
  
+ " Prepare for calling garbagecollect_for_testing().
+ let v:testing = 1
+ 
  " Support function: get the alloc ID by name.
  function GetAllocId(name)
    exe 'split ' . s:srcdir . '/alloc.h'
*** ../vim-7.4.1726/src/testdir/test_channel.vim        2016-04-07 
21:40:34.066966030 +0200
--- src/testdir/test_channel.vim        2016-04-14 11:48:31.497576059 +0200
***************
*** 183,189 ****
    call assert_equal('got it', s:responseMsg)
  
    " Collect garbage, tests that our handle isn't collected.
!   call garbagecollect()
  
    " check setting options (without testing the effect)
    call ch_setoptions(handle, {'callback': 's:NotUsed'})
--- 183,189 ----
    call assert_equal('got it', s:responseMsg)
  
    " Collect garbage, tests that our handle isn't collected.
!   call garbagecollect_for_testing()
  
    " check setting options (without testing the effect)
    call ch_setoptions(handle, {'callback': 's:NotUsed'})
***************
*** 1231,1237 ****
    call assert_fails('call job_start("")', 'E474:')
  endfunc
  
! " This leaking memory.
  func Test_partial_in_channel_cycle()
    let d = {}
    let d.a = function('string', [d])
--- 1231,1237 ----
    call assert_fails('call job_start("")', 'E474:')
  endfunc
  
! " This was leaking memory.
  func Test_partial_in_channel_cycle()
    let d = {}
    let d.a = function('string', [d])
***************
*** 1243,1247 ****
--- 1243,1255 ----
    unlet d
  endfunc
  
+ func Test_using_freed_memory()
+   let g:a = job_start(['ls'])
+   sleep 10m
+   call garbagecollect_for_testing()
+ endfunc
+ 
+ 
+ 
  " Uncomment this to see what happens, output is in src/testdir/channellog.
  " call ch_logfile('channellog', 'w')
*** ../vim-7.4.1726/runtime/doc/eval.txt        2016-04-03 20:57:17.009726516 
+0200
--- runtime/doc/eval.txt        2016-04-14 11:31:50.120078822 +0200
***************
*** 1705,1710 ****
--- 1723,1731 ----
                always 95 or bigger).  Pc is always zero.
                {only when compiled with |+termresponse| feature}
  
+                                       *v:testing* *testing-variable*
+ v:testing     Must be set before using `garbagecollect_for_testing()`.
+ 
                                *v:this_session* *this_session-variable*
  v:this_session        Full filename of the last loaded or saved session file. 
 See
                |:mksession|.  It is allowed to set this variable.  When no
***************
*** 1887,1895 ****
  foldtext()                    String  line displayed for closed fold
  foldtextresult( {lnum})               String  text for closed fold at {lnum}
  foreground()                  Number  bring the Vim window to the foreground
! function({name} [, {arglist}] [, {dict}])
                                Funcref reference to function {name}
  garbagecollect( [{atexit}])   none    free memory, breaking cyclic references
  get( {list}, {idx} [, {def}]) any     get item {idx} from {list} or {def}
  get( {dict}, {key} [, {def}]) any     get item {key} from {dict} or {def}
  getbufline( {expr}, {lnum} [, {end}])
--- 1908,1917 ----
  foldtext()                    String  line displayed for closed fold
  foldtextresult( {lnum})               String  text for closed fold at {lnum}
  foreground()                  Number  bring the Vim window to the foreground
! function( {name} [, {arglist}] [, {dict}])
                                Funcref reference to function {name}
  garbagecollect( [{atexit}])   none    free memory, breaking cyclic references
+ garbagecollect_for_testing()  none    free memory right now
  get( {list}, {idx} [, {def}]) any     get item {idx} from {list} or {def}
  get( {dict}, {key} [, {def}]) any     get item {key} from {dict} or {def}
  getbufline( {expr}, {lnum} [, {end}])
***************
*** 3635,3653 ****
  
  
  garbagecollect([{atexit}])                            *garbagecollect()*
!               Cleanup unused |Lists| and |Dictionaries| that have circular
!               references.  There is hardly ever a need to invoke this
!               function, as it is automatically done when Vim runs out of
!               memory or is waiting for the user to press a key after
!               'updatetime'.  Items without circular references are always
!               freed when they become unused.
                This is useful if you have deleted a very big |List| and/or
                |Dictionary| with circular references in a script that runs
                for a long time.
                When the optional {atexit} argument is one, garbage
                collection will also be done when exiting Vim, if it wasn't
                done before.  This is useful when checking for memory leaks.
  
  get({list}, {idx} [, {default}])                      *get()*
                Get item {idx} from |List| {list}.  When this item is not
                available return {default}.  Return zero when {default} is
--- 3678,3704 ----
  
  
  garbagecollect([{atexit}])                            *garbagecollect()*
!               Cleanup unused |Lists|, |Dictionaries|, |Channels| and |Jobs|
!               that have circular references.
!               
!               There is hardly ever a need to invoke this function, as it is
!               automatically done when Vim runs out of memory or is waiting
!               for the user to press a key after 'updatetime'.  Items without
!               circular references are always freed when they become unused.
                This is useful if you have deleted a very big |List| and/or
                |Dictionary| with circular references in a script that runs
                for a long time.
+ 
                When the optional {atexit} argument is one, garbage
                collection will also be done when exiting Vim, if it wasn't
                done before.  This is useful when checking for memory leaks.
  
+ garbagecollect_for_testing()                   *garbagecollect_for_testing()*
+               Like garbagecollect(), but executed right away.  This must
+               only be called directly to avoid any structure to exist
+               internally, and |v:testing| must have been set before calling
+               any function.
+ 
  get({list}, {idx} [, {default}])                      *get()*
                Get item {idx} from |List| {list}.  When this item is not
                available return {default}.  Return zero when {default} is
*** ../vim-7.4.1726/src/version.c       2016-04-13 21:14:31.256124229 +0200
--- src/version.c       2016-04-14 11:17:53.612778061 +0200
***************
*** 750,751 ****
--- 750,753 ----
  {   /* Add new patch number below this line */
+ /**/
+     1727,
  /**/

-- 
GOD: That is your purpose Arthur ... the Quest for the Holy Grail ...
                 "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

 /// 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