Patch 8.2.2967
Problem:    Vim9: crash when using two levels of partials.
Solution:   Add outer_ref_T and use it in the execution context.
Files:      src/structs.h, src/vim9execute.c, src/testdir/test_vim9_func.vim


*** ../vim-8.2.2966/src/structs.h       2021-05-26 22:32:06.254066656 +0200
--- src/structs.h       2021-06-09 17:48:01.565325109 +0200
***************
*** 1995,2001 ****
      garray_T  *out_stack;         // stack from outer scope
      int               out_frame_idx;      // index of stack frame in out_stack
      outer_T   *out_up;            // outer scope of outer scope or NULL
!     int               out_up_is_copy;     // don't free out_up
  };
  
  struct partial_S
--- 1995,2001 ----
      garray_T  *out_stack;         // stack from outer scope
      int               out_frame_idx;      // index of stack frame in out_stack
      outer_T   *out_up;            // outer scope of outer scope or NULL
!     partial_T *out_up_partial;    // partial owning out_up or NULL
  };
  
  struct partial_S
*** ../vim-8.2.2966/src/vim9execute.c   2021-06-06 17:34:10.324945725 +0200
--- src/vim9execute.c   2021-06-09 19:23:50.661691083 +0200
***************
*** 43,48 ****
--- 43,56 ----
      int               floc_restore_cmdmod_stacklen;
  } funclocal_T;
  
+ // Structure to hold a reference to an outer_T, with information of whether it
+ // was allocated.
+ typedef struct {
+     outer_T   *or_outer;
+     partial_T *or_partial;    // decrement "or_partial->pt_refcount" later
+     int               or_outer_allocated;  // free "or_outer" later
+ } outer_ref_T;
+ 
  // A stack is used to store:
  // - arguments passed to a :def function
  // - info about the calling function, to use when returning
***************
*** 70,76 ****
      int               ec_frame_idx;   // index in ec_stack: context of 
ec_dfunc_idx
      int               ec_initial_frame_idx;   // frame index when called
  
!     outer_T   *ec_outer;      // outer scope used for closures, allocated
      funclocal_T ec_funclocal;
  
      garray_T  ec_trystack;    // stack of trycmd_T values
--- 78,84 ----
      int               ec_frame_idx;   // index in ec_stack: context of 
ec_dfunc_idx
      int               ec_initial_frame_idx;   // frame index when called
  
!     outer_ref_T       *ec_outer_ref;  // outer scope used for closures, 
allocated
      funclocal_T ec_funclocal;
  
      garray_T  ec_trystack;    // stack of trycmd_T values
***************
*** 143,149 ****
   * Call compiled function "cdf_idx" from compiled code.
   * This adds a stack frame and sets the instruction pointer to the start of 
the
   * called function.
!  * If "pt" is not null use "pt->pt_outer" for ec_outer.
   *
   * Stack has:
   * - current arguments (already there)
--- 151,157 ----
   * Call compiled function "cdf_idx" from compiled code.
   * This adds a stack frame and sets the instruction pointer to the start of 
the
   * called function.
!  * If "pt" is not null use "pt->pt_outer" for ec_outer_ref->or_outer.
   *
   * Stack has:
   * - current arguments (already there)
***************
*** 280,286 ****
      STACK_TV_BOT(STACK_FRAME_FUNC_OFF)->vval.v_number = ectx->ec_dfunc_idx;
      STACK_TV_BOT(STACK_FRAME_IIDX_OFF)->vval.v_number = ectx->ec_iidx;
      STACK_TV_BOT(STACK_FRAME_INSTR_OFF)->vval.v_string = (void 
*)ectx->ec_instr;
!     STACK_TV_BOT(STACK_FRAME_OUTER_OFF)->vval.v_string = (void 
*)ectx->ec_outer;
      STACK_TV_BOT(STACK_FRAME_FUNCLOCAL_OFF)->vval.v_string = (void *)floc;
      STACK_TV_BOT(STACK_FRAME_IDX_OFF)->vval.v_number = ectx->ec_frame_idx;
      ectx->ec_frame_idx = ectx->ec_stack.ga_len;
--- 288,295 ----
      STACK_TV_BOT(STACK_FRAME_FUNC_OFF)->vval.v_number = ectx->ec_dfunc_idx;
      STACK_TV_BOT(STACK_FRAME_IIDX_OFF)->vval.v_number = ectx->ec_iidx;
      STACK_TV_BOT(STACK_FRAME_INSTR_OFF)->vval.v_string = (void 
*)ectx->ec_instr;
!     STACK_TV_BOT(STACK_FRAME_OUTER_OFF)->vval.v_string =
!                                                   (void *)ectx->ec_outer_ref;
      STACK_TV_BOT(STACK_FRAME_FUNCLOCAL_OFF)->vval.v_string = (void *)floc;
      STACK_TV_BOT(STACK_FRAME_IDX_OFF)->vval.v_number = ectx->ec_frame_idx;
      ectx->ec_frame_idx = ectx->ec_stack.ga_len;
***************
*** 300,329 ****
      if (pt != NULL || ufunc->uf_partial != NULL
                                             || (ufunc->uf_flags & FC_CLOSURE))
      {
!       outer_T *outer = ALLOC_CLEAR_ONE(outer_T);
  
!       if (outer == NULL)
            return FAIL;
        if (pt != NULL)
        {
!           *outer = pt->pt_outer;
!           outer->out_up_is_copy = TRUE;
        }
        else if (ufunc->uf_partial != NULL)
        {
!           *outer = ufunc->uf_partial->pt_outer;
!           outer->out_up_is_copy = TRUE;
        }
        else
        {
!           outer->out_stack = &ectx->ec_stack;
!           outer->out_frame_idx = ectx->ec_frame_idx;
!           outer->out_up = ectx->ec_outer;
        }
!       ectx->ec_outer = outer;
      }
      else
!       ectx->ec_outer = NULL;
  
      ++ufunc->uf_calls;
  
--- 309,348 ----
      if (pt != NULL || ufunc->uf_partial != NULL
                                             || (ufunc->uf_flags & FC_CLOSURE))
      {
!       outer_ref_T *ref = ALLOC_CLEAR_ONE(outer_ref_T);
  
!       if (ref == NULL)
            return FAIL;
        if (pt != NULL)
        {
!           ref->or_outer = &pt->pt_outer;
!           ++pt->pt_refcount;
!           ref->or_partial = pt;
        }
        else if (ufunc->uf_partial != NULL)
        {
!           ref->or_outer = &ufunc->uf_partial->pt_outer;
!           ++ufunc->uf_partial->pt_refcount;
!           ref->or_partial = ufunc->uf_partial;
        }
        else
        {
!           ref->or_outer = ALLOC_CLEAR_ONE(outer_T);
!           if (ref->or_outer == NULL)
!           {
!               vim_free(ref);
!               return FAIL;
!           }
!           ref->or_outer_allocated = TRUE;
!           ref->or_outer->out_stack = &ectx->ec_stack;
!           ref->or_outer->out_frame_idx = ectx->ec_frame_idx;
!           if (ectx->ec_outer_ref != NULL)
!               ref->or_outer->out_up = ectx->ec_outer_ref->or_outer;
        }
!       ectx->ec_outer_ref = ref;
      }
      else
!       ectx->ec_outer_ref = NULL;
  
      ++ufunc->uf_calls;
  
***************
*** 476,482 ****
                pt->pt_funcstack = funcstack;
                pt->pt_outer.out_stack = &funcstack->fs_ga;
                pt->pt_outer.out_frame_idx = ectx->ec_frame_idx - top;
-               pt->pt_outer.out_up = ectx->ec_outer;
            }
        }
      }
--- 495,500 ----
***************
*** 587,593 ****
      if (ret_idx == ectx->ec_frame_idx + STACK_FRAME_IDX_OFF)
        ret_idx = 0;
  
!     vim_free(ectx->ec_outer);
  
      // Restore the previous frame.
      ectx->ec_dfunc_idx = prev_dfunc_idx;
--- 605,617 ----
      if (ret_idx == ectx->ec_frame_idx + STACK_FRAME_IDX_OFF)
        ret_idx = 0;
  
!     if (ectx->ec_outer_ref != NULL)
!     {
!       if (ectx->ec_outer_ref->or_outer_allocated)
!           vim_free(ectx->ec_outer_ref->or_outer);
!       partial_unref(ectx->ec_outer_ref->or_partial);
!       vim_free(ectx->ec_outer_ref);
!     }
  
      // Restore the previous frame.
      ectx->ec_dfunc_idx = prev_dfunc_idx;
***************
*** 595,601 ****
                                        + STACK_FRAME_IIDX_OFF)->vval.v_number;
      ectx->ec_instr = (void *)STACK_TV(ectx->ec_frame_idx
                                       + STACK_FRAME_INSTR_OFF)->vval.v_string;
!     ectx->ec_outer = (void *)STACK_TV(ectx->ec_frame_idx
                                       + STACK_FRAME_OUTER_OFF)->vval.v_string;
      floc = (void *)STACK_TV(ectx->ec_frame_idx
                                   + STACK_FRAME_FUNCLOCAL_OFF)->vval.v_string;
--- 619,625 ----
                                        + STACK_FRAME_IIDX_OFF)->vval.v_number;
      ectx->ec_instr = (void *)STACK_TV(ectx->ec_frame_idx
                                       + STACK_FRAME_INSTR_OFF)->vval.v_string;
!     ectx->ec_outer_ref = (void *)STACK_TV(ectx->ec_frame_idx
                                       + STACK_FRAME_OUTER_OFF)->vval.v_string;
      floc = (void *)STACK_TV(ectx->ec_frame_idx
                                   + STACK_FRAME_FUNCLOCAL_OFF)->vval.v_string;
***************
*** 696,702 ****
   * If the function is compiled this will add a stack frame and set the
   * instruction pointer at the start of the function.
   * Otherwise the function is called here.
!  * If "pt" is not null use "pt->pt_outer" for ec_outer.
   * "iptr" can be used to replace the instruction with a more efficient one.
   */
      static int
--- 720,726 ----
   * If the function is compiled this will add a stack frame and set the
   * instruction pointer at the start of the function.
   * Otherwise the function is called here.
!  * If "pt" is not null use "pt->pt_outer" for ec_outer_ref->or_outer.
   * "iptr" can be used to replace the instruction with a more efficient one.
   */
      static int
***************
*** 1295,1318 ****
        dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
                                                          + ectx->ec_dfunc_idx;
  
!       // The closure needs to find arguments and local
!       // variables in the current stack.
        pt->pt_outer.out_stack = &ectx->ec_stack;
        pt->pt_outer.out_frame_idx = ectx->ec_frame_idx;
!       pt->pt_outer.out_up = ectx->ec_outer;
!       pt->pt_outer.out_up_is_copy = TRUE;
  
!       // If this function returns and the closure is still
!       // being used, we need to make a copy of the context
!       // (arguments and local variables). Store a reference
!       // to the partial so we can handle that.
        if (ga_grow(&ectx->ec_funcrefs, 1) == FAIL)
        {
            vim_free(pt);
            return FAIL;
        }
!       // Extra variable keeps the count of closures created
!       // in the current function call.
        ++(((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx
                       + STACK_FRAME_SIZE + dfunc->df_varcount)->vval.v_number;
  
--- 1319,1349 ----
        dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
                                                          + ectx->ec_dfunc_idx;
  
!       // The closure may need to find arguments and local variables in the
!       // current stack.
        pt->pt_outer.out_stack = &ectx->ec_stack;
        pt->pt_outer.out_frame_idx = ectx->ec_frame_idx;
!       if (ectx->ec_outer_ref != NULL)
!       {
!           // The current context already has a context, link to that one.
!           pt->pt_outer.out_up = ectx->ec_outer_ref->or_outer;
!           if (ectx->ec_outer_ref->or_partial != NULL)
!           {
!               pt->pt_outer.out_up_partial = ectx->ec_outer_ref->or_partial;
!               ++pt->pt_outer.out_up_partial->pt_refcount;
!           }
!       }
  
!       // If this function returns and the closure is still being used, we
!       // need to make a copy of the context (arguments and local variables).
!       // Store a reference to the partial so we can handle that.
        if (ga_grow(&ectx->ec_funcrefs, 1) == FAIL)
        {
            vim_free(pt);
            return FAIL;
        }
!       // Extra variable keeps the count of closures created in the current
!       // function call.
        ++(((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx
                       + STACK_FRAME_SIZE + dfunc->df_varcount)->vval.v_number;
  
***************
*** 2355,2361 ****
            case ISN_STOREOUTER:
                {
                    int         depth = iptr->isn_arg.outer.outer_depth;
!                   outer_T     *outer = ectx->ec_outer;
  
                    while (depth > 1 && outer != NULL)
                    {
--- 2386,2393 ----
            case ISN_STOREOUTER:
                {
                    int         depth = iptr->isn_arg.outer.outer_depth;
!                   outer_T     *outer = ectx->ec_outer_ref == NULL ? NULL
!                                               : ectx->ec_outer_ref->or_outer;
  
                    while (depth > 1 && outer != NULL)
                    {
***************
*** 2774,2780 ****
                }
                break;
  
!           // push a function reference to a compiled function
            case ISN_FUNCREF:
                {
                    partial_T   *pt = ALLOC_CLEAR_ONE(partial_T);
--- 2806,2812 ----
                }
                break;
  
!           // push a partial, a reference to a compiled function
            case ISN_FUNCREF:
                {
                    partial_T   *pt = ALLOC_CLEAR_ONE(partial_T);
***************
*** 2791,2797 ****
                    if (fill_partial_and_closure(pt, pt_dfunc->df_ufunc,
                                                                 ectx) == FAIL)
                        goto theend;
- 
                    tv = STACK_TV_BOT(0);
                    ++ectx->ec_stack.ga_len;
                    tv->vval.v_partial = pt;
--- 2823,2828 ----
***************
*** 4384,4405 ****
        // by copy_func().
        if (partial != NULL || base_ufunc->uf_partial != NULL)
        {
!           ectx.ec_outer = ALLOC_CLEAR_ONE(outer_T);
!           if (ectx.ec_outer == NULL)
                goto failed_early;
            if (partial != NULL)
            {
                if (partial->pt_outer.out_stack == NULL && current_ectx != NULL)
                {
!                   if (current_ectx->ec_outer != NULL)
!                       *ectx.ec_outer = *current_ectx->ec_outer;
                }
                else
!                   *ectx.ec_outer = partial->pt_outer;
            }
            else
!               *ectx.ec_outer = base_ufunc->uf_partial->pt_outer;
!           ectx.ec_outer->out_up_is_copy = TRUE;
        }
      }
  
--- 4415,4445 ----
        // by copy_func().
        if (partial != NULL || base_ufunc->uf_partial != NULL)
        {
!           ectx.ec_outer_ref = ALLOC_CLEAR_ONE(outer_ref_T);
!           if (ectx.ec_outer_ref == NULL)
                goto failed_early;
            if (partial != NULL)
            {
                if (partial->pt_outer.out_stack == NULL && current_ectx != NULL)
                {
!                   if (current_ectx->ec_outer_ref != NULL
!                           && current_ectx->ec_outer_ref->or_outer != NULL)
!                       ectx.ec_outer_ref->or_outer =
!                                         current_ectx->ec_outer_ref->or_outer;
                }
                else
!               {
!                   ectx.ec_outer_ref->or_outer = &partial->pt_outer;
!                   ++partial->pt_refcount;
!                   ectx.ec_outer_ref->or_partial = partial;
!               }
            }
            else
!           {
!               ectx.ec_outer_ref->or_outer = &base_ufunc->uf_partial->pt_outer;
!               ++base_ufunc->uf_partial->pt_refcount;
!               ectx.ec_outer_ref->or_partial = base_ufunc->uf_partial;
!           }
        }
      }
  
***************
*** 4516,4529 ****
  
      vim_free(ectx.ec_stack.ga_data);
      vim_free(ectx.ec_trystack.ga_data);
! 
!     while (ectx.ec_outer != NULL)
      {
!       outer_T     *up = ectx.ec_outer->out_up_is_copy
!                                               ? NULL : ectx.ec_outer->out_up;
! 
!       vim_free(ectx.ec_outer);
!       ectx.ec_outer = up;
      }
  
      // Not sure if this is necessary.
--- 4556,4567 ----
  
      vim_free(ectx.ec_stack.ga_data);
      vim_free(ectx.ec_trystack.ga_data);
!     if (ectx.ec_outer_ref != NULL)
      {
!       if (ectx.ec_outer_ref->or_outer_allocated)
!           vim_free(ectx.ec_outer_ref->or_outer);
!       partial_unref(ectx.ec_outer_ref->or_partial);
!       vim_free(ectx.ec_outer_ref);
      }
  
      // Not sure if this is necessary.
*** ../vim-8.2.2966/src/testdir/test_vim9_func.vim      2021-06-08 
22:01:48.754571742 +0200
--- src/testdir/test_vim9_func.vim      2021-06-09 19:26:22.685387099 +0200
***************
*** 2128,2133 ****
--- 2128,2146 ----
    CheckScriptSuccess(lines)
  enddef
  
+ def Test_double_nested_lambda()
+   var lines =<< trim END
+       vim9script
+       def F(head: string): func(string): func(string): string
+         return (sep: string): func(string): string => ((tail: string): string 
=> {
+             return head .. sep .. tail
+           })
+       enddef
+       assert_equal('hello-there', F('hello')('-')('there'))
+   END
+   CheckScriptSuccess(lines)
+ enddef
+ 
  def Test_nested_inline_lambda()
    # TODO: use the "text" argument
    var lines =<< trim END
*** ../vim-8.2.2966/src/version.c       2021-06-09 12:33:36.908213351 +0200
--- src/version.c       2021-06-09 16:39:18.905392009 +0200
***************
*** 752,753 ****
--- 752,755 ----
  {   /* Add new patch number below this line */
+ /**/
+     2967,
  /**/

-- 
CART DRIVER: Bring out your dead!
   There are legs stick out of windows and doors.  Two MEN are fighting in the
   mud - covered from head to foot in it.  Another MAN is on his hands in
   knees shovelling mud into his mouth.  We just catch sight of a MAN falling
   into a well.
                 "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/ ///
 \\\            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/202106091730.159HUVor1154470%40masaka.moolenaar.net.

Raspunde prin e-mail lui