Patch 8.2.0684
Problem:    Vim9: memory leak when using lambda.
Solution:   Move the funccal context to the partial. Free the function when
            exiting.
Files:      src/vim9.h, src/structs.h, src/vim9execute.c, src/userfunc.c,
            src/eval.c, src/testdir/test_vim9_func.vim


*** ../vim-8.2.0683/src/vim9.h  2020-05-02 17:52:38.404147677 +0200
--- src/vim9.h  2020-05-03 15:20:52.927560314 +0200
***************
*** 260,280 ****
  };
  
  /*
-  * Structure to hold the context of a compiled function, used by closures
-  * defined in that function.
-  */
- typedef struct funcstack_S
- {
-     garray_T  fs_ga;          // contains the stack, with:
-                               // - arguments
-                               // - frame
-                               // - local variables
- 
-     int               fs_refcount;    // nr of closures referencing this 
funcstack
-     int               fs_copyID;      // for garray_T collection
- } funcstack_T;
- 
- /*
   * Info about a function defined with :def.  Used in "def_functions".
   */
  struct dfunc_S {
--- 260,265 ----
***************
*** 286,296 ****
      isn_T     *df_instr;          // function body to be executed
      int               df_instr_count;
  
-     garray_T  *df_ectx_stack;     // where compiled closure finds local vars
-     int               df_ectx_frame;      // index of function frame in 
uf_ectx_stack
-     funcstack_T       *df_funcstack;      // copy of stack for closure, used 
after
-                                   // closure context function returns
- 
      int               df_varcount;        // number of local variables
      int               df_closure_count;   // number of closures created
  };
--- 271,276 ----
*** ../vim-8.2.0683/src/structs.h       2020-05-02 17:52:38.404147677 +0200
--- src/structs.h       2020-05-03 15:21:07.199509872 +0200
***************
*** 1777,1782 ****
--- 1777,1797 ----
      typval_T  *basetv;        // base for base->method()
  } funcexe_T;
  
+ /*
+  * Structure to hold the context of a compiled function, used by closures
+  * defined in that function.
+  */
+ typedef struct funcstack_S
+ {
+     garray_T  fs_ga;          // contains the stack, with:
+                               // - arguments
+                               // - frame
+                               // - local variables
+ 
+     int               fs_refcount;    // nr of closures referencing this 
funcstack
+     int               fs_copyID;      // for garray_T collection
+ } funcstack_T;
+ 
  struct partial_S
  {
      int               pt_refcount;    // reference count
***************
*** 1786,1793 ****
--- 1801,1816 ----
                                // with pt_name
      int               pt_auto;        // when TRUE the partial was created 
for using
                                // dict.member in handle_subscript()
+ 
+     // For a compiled closure: the arguments and local variables.
+     garray_T  *pt_ectx_stack;     // where to find local vars
+     int               pt_ectx_frame;      // index of function frame in 
uf_ectx_stack
+     funcstack_T       *pt_funcstack;      // copy of stack, used after context
+                                   // function returns
+ 
      int               pt_argc;        // number of arguments
      typval_T  *pt_argv;       // arguments in allocated array
+ 
      dict_T    *pt_dict;       // dict for "self"
  };
  
*** ../vim-8.2.0683/src/vim9execute.c   2020-05-02 23:12:54.722410350 +0200
--- src/vim9execute.c   2020-05-03 15:34:45.880455439 +0200
***************
*** 232,241 ****
      ectx->ec_instr = dfunc->df_instr;
      estack_push_ufunc(ETYPE_UFUNC, dfunc->df_ufunc, 1);
  
-     // used for closures
-     ectx->ec_outer_stack = dfunc->df_ectx_stack;
-     ectx->ec_outer_frame = dfunc->df_ectx_frame;
- 
      // Decide where to start execution, handles optional arguments.
      init_instr_idx(ufunc, argcount, ectx);
  
--- 232,237 ----
***************
*** 269,275 ****
--- 265,274 ----
        tv = STACK_TV(ectx->ec_frame_idx + STACK_FRAME_SIZE
                                                   + dfunc->df_varcount + idx);
        if (tv->v_type == VAR_PARTIAL && tv->vval.v_partial->pt_refcount > 1)
+       {
            closure_in_use = TRUE;
+           break;
+       }
      }
  
      if (closure_in_use)
***************
*** 315,329 ****
        {
            tv = STACK_TV(ectx->ec_frame_idx + STACK_FRAME_SIZE
                                                   + dfunc->df_varcount + idx);
!           if (tv->v_type == VAR_PARTIAL
!                                       && tv->vval.v_partial->pt_refcount > 1)
            {
!               dfunc_T *pt_dfunc = ((dfunc_T *)def_functions.ga_data)
!                                  + tv->vval.v_partial->pt_func->uf_dfunc_idx;
!               ++funcstack->fs_refcount;
!               pt_dfunc->df_funcstack = funcstack;
!               pt_dfunc->df_ectx_stack = &funcstack->fs_ga;
!               pt_dfunc->df_ectx_frame = ectx->ec_frame_idx - top;
            }
        }
      }
--- 314,330 ----
        {
            tv = STACK_TV(ectx->ec_frame_idx + STACK_FRAME_SIZE
                                                   + dfunc->df_varcount + idx);
!           if (tv->v_type == VAR_PARTIAL)
            {
!               partial_T *partial = tv->vval.v_partial;
! 
!               if (partial->pt_refcount > 1)
!               {
!                   ++funcstack->fs_refcount;
!                   partial->pt_funcstack = funcstack;
!                   partial->pt_ectx_stack = &funcstack->fs_ga;
!                   partial->pt_ectx_frame = ectx->ec_frame_idx - top;
!               }
            }
        }
      }
***************
*** 515,521 ****
        partial_T *pt = tv->vval.v_partial;
  
        if (pt->pt_func != NULL)
!           return call_ufunc(pt->pt_func, argcount, ectx, NULL);
        name = pt->pt_name;
      }
      else if (tv->v_type == VAR_FUNC)
--- 516,530 ----
        partial_T *pt = tv->vval.v_partial;
  
        if (pt->pt_func != NULL)
!       {
!           int ret = call_ufunc(pt->pt_func, argcount, ectx, NULL);
! 
!           // closure may need the function context where it was defined
!           ectx->ec_outer_stack = pt->pt_ectx_stack;
!           ectx->ec_outer_frame = pt->pt_ectx_frame;
! 
!           return ret;
!       }
        name = pt->pt_name;
      }
      else if (tv->v_type == VAR_FUNC)
***************
*** 1434,1441 ****
  
                        // The closure needs to find arguments and local
                        // variables in the current stack.
!                       pt_dfunc->df_ectx_stack = &ectx.ec_stack;
!                       pt_dfunc->df_ectx_frame = ectx.ec_frame_idx;
  
                        // If this function returns and the closure is still
                        // used, we need to make a copy of the context
--- 1443,1450 ----
  
                        // The closure needs to find arguments and local
                        // variables in the current stack.
!                       pt->pt_ectx_stack = &ectx.ec_stack;
!                       pt->pt_ectx_frame = ectx.ec_frame_idx;
  
                        // If this function returns and the closure is still
                        // used, we need to make a copy of the context
***************
*** 2676,2700 ****
      return OK;
  }
  
- /*
-  * Mark items in a def function as used.
-  */
-     int
- set_ref_in_dfunc(ufunc_T *ufunc, int copyID)
- {
-     dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
-     int           abort = FALSE;
- 
-     if (dfunc->df_funcstack != NULL)
-     {
-       typval_T    *stack = dfunc->df_funcstack->fs_ga.ga_data;
-       int         idx;
- 
-       for (idx = 0; idx < dfunc->df_funcstack->fs_ga.ga_len; ++idx)
-           abort = abort || set_ref_in_item(stack + idx, copyID, NULL, NULL);
-     }
-     return abort;
- }
- 
  
  #endif // FEAT_EVAL
--- 2685,2689 ----
*** ../vim-8.2.0683/src/userfunc.c      2020-05-02 23:12:54.722410350 +0200
--- src/userfunc.c      2020-05-03 15:18:18.644106604 +0200
***************
*** 1016,1031 ****
  /*
   * Free a function and remove it from the list of functions.  Does not free
   * what a function contains, call func_clear() first.
   */
      static void
! func_free(ufunc_T *fp)
  {
      // Only remove it when not done already, otherwise we would remove a newer
      // version of the function with the same name.
      if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0)
        func_remove(fp);
  
!     if ((fp->uf_flags & FC_DEAD) == 0)
        vim_free(fp);
  }
  
--- 1016,1032 ----
  /*
   * Free a function and remove it from the list of functions.  Does not free
   * what a function contains, call func_clear() first.
+  * When "force" is TRUE we are exiting.
   */
      static void
! func_free(ufunc_T *fp, int force)
  {
      // Only remove it when not done already, otherwise we would remove a newer
      // version of the function with the same name.
      if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0)
        func_remove(fp);
  
!     if ((fp->uf_flags & FC_DEAD) == 0 || force)
        vim_free(fp);
  }
  
***************
*** 1037,1043 ****
  func_clear_free(ufunc_T *fp, int force)
  {
      func_clear(fp, force);
!     func_free(fp);
  }
  
  
--- 1038,1044 ----
  func_clear_free(ufunc_T *fp, int force)
  {
      func_clear(fp, force);
!     func_free(fp, force);
  }
  
  
***************
*** 1664,1670 ****
                    ++skipped;
                else
                {
!                   func_free(fp);
                    skipped = 0;
                    break;
                }
--- 1665,1671 ----
                    ++skipped;
                else
                {
!                   func_free(fp, FALSE);
                    skipped = 0;
                    break;
                }
***************
*** 4392,4399 ****
            fp = HI2UF(hi);
            if (!func_name_refcount(fp->uf_name))
                abort = abort || set_ref_in_func(NULL, fp, copyID);
-           else if (fp->uf_dfunc_idx >= 0)
-               abort = abort || set_ref_in_dfunc(fp, copyID);
        }
      }
      return abort;
--- 4393,4398 ----
***************
*** 4441,4448 ****
      {
        for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped)
            abort = abort || set_ref_in_funccal(fc, copyID);
-       if (fp->uf_dfunc_idx >= 0)
-           abort = abort || set_ref_in_dfunc(fp, copyID);
      }
  
      vim_free(tofree);
--- 4440,4445 ----
*** ../vim-8.2.0683/src/eval.c  2020-04-30 22:29:36.622024155 +0200
--- src/eval.c  2020-05-03 15:34:35.976491769 +0200
***************
*** 3703,3708 ****
--- 3703,3726 ----
      }
      else
        func_ptr_unref(pt->pt_func);
+ 
+     if (pt->pt_funcstack != NULL)
+     {
+       // Decrease the reference count for the context of a closure.  If down
+       // to zero free it and clear the variables on the stack.
+       if (--pt->pt_funcstack->fs_refcount == 0)
+       {
+           garray_T    *gap = &pt->pt_funcstack->fs_ga;
+           typval_T    *stack = gap->ga_data;
+ 
+           for (i = 0; i < gap->ga_len; ++i)
+               clear_tv(stack + i);
+           ga_clear(gap);
+           vim_free(pt->pt_funcstack);
+       }
+       pt->pt_funcstack = NULL;
+     }
+ 
      vim_free(pt);
  }
  
***************
*** 4336,4341 ****
--- 4354,4368 ----
            for (i = 0; i < pt->pt_argc; ++i)
                abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID,
                                                        ht_stack, list_stack);
+           if (pt->pt_funcstack != NULL)
+           {
+               typval_T    *stack = pt->pt_funcstack->fs_ga.ga_data;
+ 
+               for (i = 0; i < pt->pt_funcstack->fs_ga.ga_len; ++i)
+                   abort = abort || set_ref_in_item(stack + i, copyID,
+                                                        ht_stack, list_stack);
+           }
+ 
        }
      }
  #ifdef FEAT_JOB_CHANNEL
*** ../vim-8.2.0683/src/testdir/test_vim9_func.vim      2020-05-02 
23:12:54.726410335 +0200
--- src/testdir/test_vim9_func.vim      2020-05-03 14:55:31.433221437 +0200
***************
*** 680,692 ****
    unlet g:Read
  enddef
  
- " TODO: fix memory leak when using same function again.
- def MakeTwoRefs_2()
-   let local = ['some']
-   g:Extend = {s -> local->add(s)}
-   g:Read = {-> local}
- enddef
- 
  def ReadRef(Ref: func(): list<string>): string
    return join(Ref(), ' ')
  enddef
--- 680,685 ----
***************
*** 696,702 ****
  enddef
  
  def Test_closure_two_indirect_refs()
!   MakeTwoRefs_2()
    assert_equal('some', ReadRef(g:Read))
    ExtendRef(g:Extend, 'more')
    assert_equal('some more', ReadRef(g:Read))
--- 689,695 ----
  enddef
  
  def Test_closure_two_indirect_refs()
!   MakeTwoRefs()
    assert_equal('some', ReadRef(g:Read))
    ExtendRef(g:Extend, 'more')
    assert_equal('some more', ReadRef(g:Read))
***************
*** 707,710 ****
--- 700,704 ----
    unlet g:Read
  enddef
  
+ 
  " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
*** ../vim-8.2.0683/src/version.c       2020-05-02 23:12:54.726410335 +0200
--- src/version.c       2020-05-03 14:45:24.119649418 +0200
***************
*** 748,749 ****
--- 748,751 ----
  {   /* Add new patch number below this line */
+ /**/
+     684,
  /**/

-- 
Just remember...if the world didn't suck, we'd all fall off.

 /// 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].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202005031338.043Dcpik010635%40masaka.moolenaar.net.

Raspunde prin e-mail lui