changeset 25656:fe7f45e2895e v8.2.3364

patch 8.2.3364: Vim9: crash when :for is skipped Commit: https://github.com/vim/vim/commit/bebf06954e1c801870b57e06ab03151c2654d079 Author: rbtnn <naru123456789@gmail.com> Date: Sat Aug 21 17:26:50 2021 +0200 patch 8.2.3364: Vim9: crash when :for is skipped Problem: Vim9: crash when :for is skipped. Solution: Skip more code generation. (Naruhiko Nishino, closes https://github.com/vim/vim/issues/8777)
author Bram Moolenaar <Bram@vim.org>
date Sat, 21 Aug 2021 17:30:03 +0200
parents cf7cd75766d9
children 26c53bca9f23
files src/testdir/test_vim9_script.vim src/version.c src/vim9compile.c
diffstat 3 files changed, 222 insertions(+), 150 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -2552,6 +2552,70 @@ def Test_for_outside_of_function()
   delete('Xvim9for.vim')
 enddef
 
+def Test_for_skipped_block()
+  # test skipped blocks at outside of function
+  var lines =<< trim END
+    var result = []
+    if true
+      for n in [1, 2]
+        result += [n]
+      endfor
+    else
+      for n in [3, 4]
+        result += [n]
+      endfor
+    endif
+    assert_equal([1, 2], result)
+
+    result = []
+    if false
+      for n in [1, 2]
+        result += [n]
+      endfor
+    else
+      for n in [3, 4]
+        result += [n]
+      endfor
+    endif
+    assert_equal([3, 4], result)
+  END
+  CheckDefAndScriptSuccess(lines)
+
+  # test skipped blocks at inside of function
+  lines =<< trim END
+    def DefTrue()
+      var result = []
+      if true
+        for n in [1, 2]
+          result += [n]
+        endfor
+      else
+        for n in [3, 4]
+          result += [n]
+        endfor
+      endif
+      assert_equal([1, 2], result)
+    enddef
+    DefTrue()
+
+    def DefFalse()
+      var result = []
+      if false
+        for n in [1, 2]
+          result += [n]
+        endfor
+      else
+        for n in [3, 4]
+          result += [n]
+        endfor
+      endif
+      assert_equal([3, 4], result)
+    enddef
+    DefFalse()
+  END
+  CheckDefAndScriptSuccess(lines)
+enddef
+
 def Test_for_loop()
   var lines =<< trim END
       var result = ''
--- a/src/version.c
+++ b/src/version.c
@@ -756,6 +756,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3364,
+/**/
     3363,
 /**/
     3362,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -8041,151 +8041,154 @@ compile_for(char_u *arg_start, cctx_T *c
     }
     arg_end = arg;
 
-    // If we know the type of "var" and it is a not a supported type we can
-    // give an error now.
-    vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
-    if (vartype->tt_type != VAR_LIST && vartype->tt_type != VAR_STRING
+    if (cctx->ctx_skip != SKIP_YES)
+    {
+	// If we know the type of "var" and it is a not a supported type we can
+	// give an error now.
+	vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+	if (vartype->tt_type != VAR_LIST && vartype->tt_type != VAR_STRING
 		&& vartype->tt_type != VAR_BLOB && vartype->tt_type != VAR_ANY)
-    {
-	semsg(_(e_for_loop_on_str_not_supported),
+	{
+	    semsg(_(e_for_loop_on_str_not_supported),
 					       vartype_name(vartype->tt_type));
-	drop_scope(cctx);
-	return NULL;
-    }
-
-    if (vartype->tt_type == VAR_STRING)
-	item_type = &t_string;
-    else if (vartype->tt_type == VAR_BLOB)
-	item_type = &t_number;
-    else if (vartype->tt_type == VAR_LIST
-				     && vartype->tt_member->tt_type != VAR_ANY)
-    {
-	if (!var_list)
-	    item_type = vartype->tt_member;
-	else if (vartype->tt_member->tt_type == VAR_LIST
-		      && vartype->tt_member->tt_member->tt_type != VAR_ANY)
-	    // TODO: should get the type for each lhs
-	    item_type = vartype->tt_member->tt_member;
-    }
-
-    // CMDMOD_REV must come before the FOR instruction.
-    generate_undo_cmdmods(cctx);
-
-    // "for_end" is set when ":endfor" is found
-    scope->se_u.se_for.fs_top_label = current_instr_idx(cctx);
-
-    generate_FOR(cctx, loop_lvar->lv_idx);
-
-    arg = arg_start;
-    if (var_list)
-    {
-	generate_UNPACK(cctx, var_count, semicolon);
-	arg = skipwhite(arg + 1);	// skip white after '['
-
-	// the list item is replaced by a number of items
-	if (GA_GROW_FAILS(stack, var_count - 1))
-	{
 	    drop_scope(cctx);
 	    return NULL;
 	}
-	--stack->ga_len;
+
+	if (vartype->tt_type == VAR_STRING)
+	    item_type = &t_string;
+	else if (vartype->tt_type == VAR_BLOB)
+	    item_type = &t_number;
+	else if (vartype->tt_type == VAR_LIST
+				     && vartype->tt_member->tt_type != VAR_ANY)
+	{
+	    if (!var_list)
+		item_type = vartype->tt_member;
+	    else if (vartype->tt_member->tt_type == VAR_LIST
+			  && vartype->tt_member->tt_member->tt_type != VAR_ANY)
+		// TODO: should get the type for each lhs
+		item_type = vartype->tt_member->tt_member;
+	}
+
+	// CMDMOD_REV must come before the FOR instruction.
+	generate_undo_cmdmods(cctx);
+
+	// "for_end" is set when ":endfor" is found
+	scope->se_u.se_for.fs_top_label = current_instr_idx(cctx);
+
+	generate_FOR(cctx, loop_lvar->lv_idx);
+
+	arg = arg_start;
+	if (var_list)
+	{
+	    generate_UNPACK(cctx, var_count, semicolon);
+	    arg = skipwhite(arg + 1);	// skip white after '['
+
+	    // the list item is replaced by a number of items
+	    if (GA_GROW_FAILS(stack, var_count - 1))
+	    {
+		drop_scope(cctx);
+		return NULL;
+	    }
+	    --stack->ga_len;
+	    for (idx = 0; idx < var_count; ++idx)
+	    {
+		((type_T **)stack->ga_data)[stack->ga_len] =
+				 (semicolon && idx == 0) ? vartype : item_type;
+		++stack->ga_len;
+	    }
+	}
+
 	for (idx = 0; idx < var_count; ++idx)
 	{
-	    ((type_T **)stack->ga_data)[stack->ga_len] =
-				(semicolon && idx == 0) ? vartype : item_type;
-	    ++stack->ga_len;
-	}
-    }
-
-    for (idx = 0; idx < var_count; ++idx)
-    {
-	assign_dest_T	dest = dest_local;
-	int		opt_flags = 0;
-	int		vimvaridx = -1;
-	type_T		*type = &t_any;
-	type_T		*lhs_type = &t_any;
-	where_T		where = WHERE_INIT;
-
-	p = skip_var_one(arg, FALSE);
-	varlen = p - arg;
-	name = vim_strnsave(arg, varlen);
-	if (name == NULL)
-	    goto failed;
-	if (*p == ':')
-	{
-	    p = skipwhite(p + 1);
-	    lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE);
-	}
-
-	// TODO: script var not supported?
-	if (get_var_dest(name, &dest, CMD_for, &opt_flags,
-					      &vimvaridx, &type, cctx) == FAIL)
-	    goto failed;
-	if (dest != dest_local)
-	{
-	    if (generate_store_var(cctx, dest, opt_flags, vimvaridx,
-						     0, 0, type, name) == FAIL)
-		goto failed;
-	}
-	else if (varlen == 1 && *arg == '_')
-	{
-	    // Assigning to "_": drop the value.
-	    if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
+	    assign_dest_T	dest = dest_local;
+	    int		opt_flags = 0;
+	    int		vimvaridx = -1;
+	    type_T		*type = &t_any;
+	    type_T		*lhs_type = &t_any;
+	    where_T		where = WHERE_INIT;
+
+	    p = skip_var_one(arg, FALSE);
+	    varlen = p - arg;
+	    name = vim_strnsave(arg, varlen);
+	    if (name == NULL)
 		goto failed;
-	}
-	else
-	{
-	    if (lookup_local(arg, varlen, NULL, cctx) == OK)
-	    {
-		semsg(_(e_variable_already_declared), arg);
-		goto failed;
-	    }
-
-	    if (STRNCMP(name, "s:", 2) == 0)
-	    {
-		semsg(_(e_cannot_declare_script_variable_in_function), name);
-		goto failed;
-	    }
-
-	    // Reserve a variable to store "var".
-	    where.wt_index = var_list ? idx + 1 : 0;
-	    where.wt_variable = TRUE;
-	    if (lhs_type == &t_any)
-		lhs_type = item_type;
-	    else if (item_type != &t_unknown
-			&& (item_type == &t_any
-			  ? need_type(item_type, lhs_type,
-						     -1, 0, cctx, FALSE, FALSE)
-			  : check_type(lhs_type, item_type, TRUE, where))
-			== FAIL)
+	    if (*p == ':')
+	    {
+		p = skipwhite(p + 1);
+		lhs_type = parse_type(&p, cctx->ctx_type_list, TRUE);
+	    }
+
+	    // TODO: script var not supported?
+	    if (get_var_dest(name, &dest, CMD_for, &opt_flags,
+					      &vimvaridx, &type, cctx) == FAIL)
 		goto failed;
-	    var_lvar = reserve_local(cctx, arg, varlen, TRUE, lhs_type);
-	    if (var_lvar == NULL)
-		// out of memory or used as an argument
-		goto failed;
-
-	    if (semicolon && idx == var_count - 1)
-		var_lvar->lv_type = vartype;
+	    if (dest != dest_local)
+	    {
+		if (generate_store_var(cctx, dest, opt_flags, vimvaridx,
+						     0, 0, type, name) == FAIL)
+		    goto failed;
+	    }
+	    else if (varlen == 1 && *arg == '_')
+	    {
+		// Assigning to "_": drop the value.
+		if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
+		    goto failed;
+	    }
 	    else
-		var_lvar->lv_type = item_type;
-	    generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
-	}
-
-	if (*p == ',' || *p == ';')
-	    ++p;
-	arg = skipwhite(p);
-	vim_free(name);
-    }
-
-    if (cctx->ctx_compile_type == CT_DEBUG)
-    {
-	int save_prev_lnum = cctx->ctx_prev_lnum;
-
-	// Add ISN_DEBUG here, so that the loop variables can be inspected.
-	// Use the prev_lnum from the ISN_DEBUG instruction removed above.
-	cctx->ctx_prev_lnum = prev_lnum;
-	generate_instr_debug(cctx);
-	cctx->ctx_prev_lnum = save_prev_lnum;
+	    {
+		if (lookup_local(arg, varlen, NULL, cctx) == OK)
+		{
+		    semsg(_(e_variable_already_declared), arg);
+		    goto failed;
+		}
+
+		if (STRNCMP(name, "s:", 2) == 0)
+		{
+		    semsg(_(e_cannot_declare_script_variable_in_function), name);
+		    goto failed;
+		}
+
+		// Reserve a variable to store "var".
+		where.wt_index = var_list ? idx + 1 : 0;
+		where.wt_variable = TRUE;
+		if (lhs_type == &t_any)
+		    lhs_type = item_type;
+		else if (item_type != &t_unknown
+			    && (item_type == &t_any
+			      ? need_type(item_type, lhs_type,
+						     -1, 0, cctx, FALSE, FALSE)
+			      : check_type(lhs_type, item_type, TRUE, where))
+			    == FAIL)
+		    goto failed;
+		var_lvar = reserve_local(cctx, arg, varlen, TRUE, lhs_type);
+		if (var_lvar == NULL)
+		    // out of memory or used as an argument
+		    goto failed;
+
+		if (semicolon && idx == var_count - 1)
+		    var_lvar->lv_type = vartype;
+		else
+		    var_lvar->lv_type = item_type;
+		generate_STORE(cctx, ISN_STORE, var_lvar->lv_idx, NULL);
+	    }
+
+	    if (*p == ',' || *p == ';')
+		++p;
+	    arg = skipwhite(p);
+	    vim_free(name);
+	}
+
+	if (cctx->ctx_compile_type == CT_DEBUG)
+	{
+	    int save_prev_lnum = cctx->ctx_prev_lnum;
+
+	    // Add ISN_DEBUG here, so that the loop variables can be inspected.
+	    // Use the prev_lnum from the ISN_DEBUG instruction removed above.
+	    cctx->ctx_prev_lnum = prev_lnum;
+	    generate_instr_debug(cctx);
+	    cctx->ctx_prev_lnum = save_prev_lnum;
+	}
     }
 
     return arg_end;
@@ -8217,21 +8220,24 @@ compile_endfor(char_u *arg, cctx_T *cctx
     }
     forscope = &scope->se_u.se_for;
     cctx->ctx_scope = scope->se_outer;
-    unwind_locals(cctx, scope->se_local_count);
-
-    // At end of ":for" scope jump back to the FOR instruction.
-    generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label);
-
-    // Fill in the "end" label in the FOR statement so it can jump here.
-    isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label;
-    isn->isn_arg.forloop.for_end = instr->ga_len;
-
-    // Fill in the "end" label any BREAK statements
-    compile_fill_jump_to_end(&forscope->fs_end_label, instr->ga_len, cctx);
-
-    // Below the ":for" scope drop the "expr" list from the stack.
-    if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
-	return NULL;
+    if (cctx->ctx_skip != SKIP_YES)
+    {
+	unwind_locals(cctx, scope->se_local_count);
+
+	// At end of ":for" scope jump back to the FOR instruction.
+	generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label);
+
+	// Fill in the "end" label in the FOR statement so it can jump here.
+	isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label;
+	isn->isn_arg.forloop.for_end = instr->ga_len;
+
+	// Fill in the "end" label any BREAK statements
+	compile_fill_jump_to_end(&forscope->fs_end_label, instr->ga_len, cctx);
+
+	// Below the ":for" scope drop the "expr" list from the stack.
+	if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL)
+	    return NULL;
+    }
 
     vim_free(scope);