]> granicus.if.org Git - vim/commitdiff
patch 8.2.3216: Vim9: crash when using variable in a loop at script level v8.2.3216
authorBram Moolenaar <Bram@vim.org>
Sun, 25 Jul 2021 12:13:53 +0000 (14:13 +0200)
committerBram Moolenaar <Bram@vim.org>
Sun, 25 Jul 2021 12:13:53 +0000 (14:13 +0200)
Problem:    Vim9: crash when using variable in a loop at script level.
Solution:   Do not clear the variable if a function was defined.
            Do not create a new entry in sn_var_vals every time.
            (closes #8628)

src/eval.c
src/evalvars.c
src/ex_eval.c
src/structs.h
src/testdir/test_vim9_script.vim
src/userfunc.c
src/version.c
src/vim9script.c

index fdee0089b3cd5534a7b7438616ee919b2b9df745..fc2266225a53688e234aecfb94871ae06affb98d 100644 (file)
@@ -146,10 +146,14 @@ fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, int skip)
 {
     CLEAR_FIELD(*evalarg);
     evalarg->eval_flags = skip ? 0 : EVAL_EVALUATE;
-    if (eap != NULL && getline_equal(eap->getline, eap->cookie, getsourceline))
+    if (eap != NULL)
     {
-       evalarg->eval_getline = eap->getline;
-       evalarg->eval_cookie = eap->cookie;
+       evalarg->eval_cstack = eap->cstack;
+       if (getline_equal(eap->getline, eap->cookie, getsourceline))
+       {
+           evalarg->eval_getline = eap->getline;
+           evalarg->eval_cookie = eap->cookie;
+       }
     }
 }
 
index 1f781dad74ae16848aa74fc8686dc6f74e3e11a2..534163c543286dc34bda113eec1d0ee323477070 100644 (file)
@@ -880,13 +880,7 @@ ex_let(exarg_T *eap)
 
            if (eap->skip)
                ++emsg_skip;
-           CLEAR_FIELD(evalarg);
-           evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE;
-           if (getline_equal(eap->getline, eap->cookie, getsourceline))
-           {
-               evalarg.eval_getline = eap->getline;
-               evalarg.eval_cookie = eap->cookie;
-           }
+           fill_evalarg_from_eap(&evalarg, eap, eap->skip);
            expr = skipwhite_and_linebreak(expr, &evalarg);
            cur_lnum = SOURCING_LNUM;
            i = eval0(expr, &rettv, eap, &evalarg);
index 6491955d6830a926c3d2fe6a8a68728931148dd6..827f9cdcdb8eec8d19980c72eb86008ae5176c1c 100644 (file)
@@ -972,9 +972,6 @@ leave_block(cstack_T *cstack)
                hide_script_var(si, i, func_defined);
        }
 
-       // TODO: is this needed?
-       cstack->cs_script_var_len[cstack->cs_idx] = si->sn_var_vals.ga_len;
-
        if (cstack->cs_idx == 0)
            si->sn_current_block_id = 0;
        else
@@ -1178,6 +1175,8 @@ ex_while(exarg_T *eap)
            {
                scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
                int             i;
+               int             func_defined = cstack->cs_flags[cstack->cs_idx]
+                                                               & CSF_FUNC_DEF;
 
                // Any variables defined in the previous round are no longer
                // visible.
@@ -1192,10 +1191,8 @@ ex_while(exarg_T *eap)
                    if (sv->sv_name != NULL)
                        // Remove a variable declared inside the block, if it
                        // still exists, from sn_vars.
-                       hide_script_var(si, i, FALSE);
+                       hide_script_var(si, i, func_defined);
                }
-               cstack->cs_script_var_len[cstack->cs_idx] =
-                                                       si->sn_var_vals.ga_len;
            }
        }
        cstack->cs_flags[cstack->cs_idx] =
@@ -1222,14 +1219,7 @@ ex_while(exarg_T *eap)
            /*
             * ":for var in list-expr"
             */
-           CLEAR_FIELD(evalarg);
-           evalarg.eval_flags = skip ? 0 : EVAL_EVALUATE;
-           if (getline_equal(eap->getline, eap->cookie, getsourceline))
-           {
-               evalarg.eval_getline = eap->getline;
-               evalarg.eval_cookie = eap->cookie;
-           }
-
+           fill_evalarg_from_eap(&evalarg, eap, skip);
            if ((cstack->cs_lflags & CSL_HAD_LOOP) != 0)
            {
                // Jumping here from a ":continue" or ":endfor": use the
index 265fe0253d637d015f64187c8c29196806525ddf..bf8102cd79534fa8e7ffdbcdb96485ef8895fbda 100644 (file)
@@ -1888,6 +1888,9 @@ typedef struct {
     // used when compiling a :def function, NULL otherwise
     cctx_T     *eval_cctx;
 
+    // used when executing commands from a script, NULL otherwise
+    cstack_T   *eval_cstack;
+
     // Used to collect lines while parsing them, so that they can be
     // concatenated later.  Used when "eval_ga.ga_itemsize" is not zero.
     // "eval_ga.ga_data" is a list of pointers to lines.
index 8c80e1801dc0f25b5ef37e2bc2ad19b3a9e48a0f..14684c0961feba69f86b9edb27f814034235f909 100644 (file)
@@ -2592,6 +2592,34 @@ def Test_for_loop()
   CheckDefAndScriptSuccess(lines)
 enddef
 
+def Test_for_loop_with_closure()
+  var lines =<< trim END
+      var flist: list<func>
+      for i in range(5)
+        var inloop = i
+        flist[i] = () => inloop
+      endfor
+      for i in range(5)
+        assert_equal(4, flist[i]())
+      endfor
+  END
+  CheckDefAndScriptSuccess(lines)
+
+  lines =<< trim END
+      var flist: list<func>
+      for i in range(5)
+        var inloop = i
+        flist[i] = () => {
+              return inloop
+            }
+      endfor
+      for i in range(5)
+        assert_equal(4, flist[i]())
+      endfor
+  END
+  CheckDefAndScriptSuccess(lines)
+enddef
+
 def Test_for_loop_fails()
   CheckDefAndScriptFailure2(['for '], 'E1097:', 'E690:')
   CheckDefAndScriptFailure2(['for x'], 'E1097:', 'E690:')
index be1eaa95737a9984a65a57d51241938ec8fc2af4..7c9634742bdfc419dc6e4d389012ad760ae712b3 100644 (file)
@@ -613,6 +613,35 @@ is_function_cmd(char_u **cmd)
     return FALSE;
 }
 
+/*
+ * Called when defining a function: The context may be needed for script
+ * variables declared in a block that is visible now but not when the function
+ * is compiled or called later.
+ */
+    static void
+function_using_block_scopes(ufunc_T *fp, cstack_T *cstack)
+{
+    if (cstack != NULL && cstack->cs_idx >= 0)
+    {
+       int         count = cstack->cs_idx + 1;
+       int         i;
+
+       fp->uf_block_ids = ALLOC_MULT(int, count);
+       if (fp->uf_block_ids != NULL)
+       {
+           mch_memmove(fp->uf_block_ids, cstack->cs_block_id,
+                                                         sizeof(int) * count);
+           fp->uf_block_depth = count;
+       }
+
+       // Set flag in each block to indicate a function was defined.  This
+       // is used to keep the variable when leaving the block, see
+       // hide_script_var().
+       for (i = 0; i <= cstack->cs_idx; ++i)
+           cstack->cs_flags[i] |= CSF_FUNC_DEF;
+    }
+}
+
 /*
  * Read the body of a function, put every line in "newlines".
  * This stops at "}", "endfunction" or "enddef".
@@ -1195,6 +1224,8 @@ lambda_function_body(
     ufunc->uf_script_ctx.sc_lnum += sourcing_lnum_top;
     set_function_type(ufunc);
 
+    function_using_block_scopes(ufunc, evalarg->eval_cstack);
+
     rettv->vval.v_partial = pt;
     rettv->v_type = VAR_PARTIAL;
     ufunc = NULL;
@@ -1442,6 +1473,8 @@ get_lambda_tv(
        fp->uf_script_ctx = current_sctx;
        fp->uf_script_ctx.sc_lnum += SOURCING_LNUM - newlines.ga_len;
 
+       function_using_block_scopes(fp, evalarg->eval_cstack);
+
        pt->pt_func = fp;
        pt->pt_refcount = 1;
        rettv->vval.v_partial = pt;
@@ -1459,6 +1492,7 @@ theend:
     vim_free(tofree2);
     if (types_optional)
        ga_clear_strings(&argtypes);
+
     return OK;
 
 errret:
@@ -4313,28 +4347,8 @@ define_function(exarg_T *eap, char_u *name_arg)
        // error messages are for the first function line
        SOURCING_LNUM = sourcing_lnum_top;
 
-       if (cstack != NULL && cstack->cs_idx >= 0)
-       {
-           int     count = cstack->cs_idx + 1;
-           int     i;
-
-           // The block context may be needed for script variables declared in
-           // a block that is visible now but not when the function is called
-           // later.
-           fp->uf_block_ids = ALLOC_MULT(int, count);
-           if (fp->uf_block_ids != NULL)
-           {
-               mch_memmove(fp->uf_block_ids, cstack->cs_block_id,
-                                                         sizeof(int) * count);
-               fp->uf_block_depth = count;
-           }
-
-           // Set flag in each block to indicate a function was defined.  This
-           // is used to keep the variable when leaving the block, see
-           // hide_script_var().
-           for (i = 0; i <= cstack->cs_idx; ++i)
-               cstack->cs_flags[i] |= CSF_FUNC_DEF;
-       }
+       // The function may use script variables from the context.
+       function_using_block_scopes(fp, cstack);
 
        if (parse_argument_types(fp, &argtypes, varargs) == FAIL)
        {
index f12b9dc04ec5b2e1b5c4982d217e63ca5f9870da..ea1411dffa58bef5e32e06911f326c2ae915182c 100644 (file)
@@ -755,6 +755,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3216,
 /**/
     3215,
 /**/
index 605d0be969047a5ab47030c4dfc9c2dab3123a5d..9b4a2c0fbb7a8340873d5edaa39ea44b8a515fcb 100644 (file)
@@ -758,47 +758,72 @@ update_vim9_script_var(
 {
     scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
     hashitem_T     *hi;
-    svar_T         *sv;
+    svar_T         *sv = NULL;
 
     if (create)
     {
-       sallvar_T           *newsav;
+       sallvar_T   *newsav;
+       sallvar_T   *sav = NULL;
 
        // Store a pointer to the typval_T, so that it can be found by index
        // instead of using a hastab lookup.
        if (ga_grow(&si->sn_var_vals, 1) == FAIL)
            return;
 
-       sv = ((svar_T *)si->sn_var_vals.ga_data) + si->sn_var_vals.ga_len;
-       newsav = (sallvar_T *)alloc_clear(
-                                      sizeof(sallvar_T) + STRLEN(di->di_key));
-       if (newsav == NULL)
-           return;
-
-       sv->sv_tv = &di->di_tv;
-       sv->sv_const = (flags & ASSIGN_FINAL) ? ASSIGN_FINAL
-                                  : (flags & ASSIGN_CONST) ? ASSIGN_CONST : 0;
-       sv->sv_export = is_export;
-       newsav->sav_var_vals_idx = si->sn_var_vals.ga_len;
-       ++si->sn_var_vals.ga_len;
-       STRCPY(&newsav->sav_key, di->di_key);
-       sv->sv_name = newsav->sav_key;
-       newsav->sav_di = di;
-       newsav->sav_block_id = si->sn_current_block_id;
-
-       hi = hash_find(&si->sn_all_vars.dv_hashtab, newsav->sav_key);
+       hi = hash_find(&si->sn_all_vars.dv_hashtab, di->di_key);
        if (!HASHITEM_EMPTY(hi))
        {
-           sallvar_T *sav = HI2SAV(hi);
+           // Variable with this name exists, either in this block or in
+           // another block.
+           for (sav = HI2SAV(hi); ; sav = sav->sav_next)
+           {
+               if (sav->sav_block_id == si->sn_current_block_id)
+               {
+                   // variable defined in a loop, re-use the entry
+                   sv = ((svar_T *)si->sn_var_vals.ga_data)
+                                                      + sav->sav_var_vals_idx;
+                   // unhide the variable
+                   if (sv->sv_tv == &sav->sav_tv)
+                   {
+                       clear_tv(&sav->sav_tv);
+                       sv->sv_tv = &di->di_tv;
+                       sav->sav_di = di;
+                   }
+                   break;
+               }
+               if (sav->sav_next == NULL)
+                   break;
+           }
+       }
+
+       if (sv == NULL)
+       {
+           // Variable not defined or not defined in current block: Add a
+           // svar_T and create a new sallvar_T.
+           sv = ((svar_T *)si->sn_var_vals.ga_data) + si->sn_var_vals.ga_len;
+           newsav = (sallvar_T *)alloc_clear(
+                                      sizeof(sallvar_T) + STRLEN(di->di_key));
+           if (newsav == NULL)
+               return;
 
-           // variable with this name exists in another block
-           while (sav->sav_next != NULL)
-               sav = sav->sav_next;
-           sav->sav_next = newsav;
+           sv->sv_tv = &di->di_tv;
+           sv->sv_const = (flags & ASSIGN_FINAL) ? ASSIGN_FINAL
+                                  : (flags & ASSIGN_CONST) ? ASSIGN_CONST : 0;
+           sv->sv_export = is_export;
+           newsav->sav_var_vals_idx = si->sn_var_vals.ga_len;
+           ++si->sn_var_vals.ga_len;
+           STRCPY(&newsav->sav_key, di->di_key);
+           sv->sv_name = newsav->sav_key;
+           newsav->sav_di = di;
+           newsav->sav_block_id = si->sn_current_block_id;
+
+           if (HASHITEM_EMPTY(hi))
+               // new variable name
+               hash_add(&si->sn_all_vars.dv_hashtab, newsav->sav_key);
+           else if (sav != NULL)
+               // existing name in a new block, append to the list
+               sav->sav_next = newsav;
        }
-       else
-           // new variable name
-           hash_add(&si->sn_all_vars.dv_hashtab, newsav->sav_key);
     }
     else
     {
@@ -807,8 +832,7 @@ update_vim9_script_var(
     if (sv != NULL)
     {
        if (*type == NULL)
-           *type = typval2type(tv, get_copyID(), &si->sn_type_list,
-                                                                   do_member);
+           *type = typval2type(tv, get_copyID(), &si->sn_type_list, do_member);
        sv->sv_type = *type;
     }