changeset 21883:a427f5f26419 v8.2.1491

patch 8.2.1491: Vim9: crash when compiling heredoc lines start with comment Commit: https://github.com/vim/vim/commit/66250c932e8a0e3c43e7c7c7b1dbede040b9c508 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Aug 20 15:02:42 2020 +0200 patch 8.2.1491: Vim9: crash when compiling heredoc lines start with comment Problem: Vim9: crash when compiling heredoc lines start with comment. Solution: Skip over NULL pointers. Do not remove comment and empty lines when fetching function lines. (closes #6743)
author Bram Moolenaar <Bram@vim.org>
date Thu, 20 Aug 2020 15:15:05 +0200
parents 80443e8b72ce
children c2f5f1b4a6e1
files src/autocmd.c src/evalfunc.c src/ex_cmds.h src/ex_docmd.c src/ex_getln.c src/proto/autocmd.pro src/proto/ex_docmd.pro src/proto/ex_getln.pro src/proto/scriptfile.pro src/proto/userfunc.pro src/scriptfile.c src/structs.h src/testdir/test_vim9_script.vim src/userfunc.c src/version.c src/vim9compile.c
diffstat 16 files changed, 84 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -2310,7 +2310,11 @@ auto_next_pat(
  * Returns allocated string, or NULL for end of autocommands.
  */
     char_u *
-getnextac(int c UNUSED, void *cookie, int indent UNUSED, int do_concat UNUSED)
+getnextac(
+	int c UNUSED,
+	void *cookie,
+	int indent UNUSED,
+	getline_opt_T options UNUSED)
 {
     AutoPatCmd	    *acp = (AutoPatCmd *)cookie;
     char_u	    *retval;
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2198,13 +2198,12 @@ execute_redir_str(char_u *value, int val
  * Called by do_cmdline() to get the next line.
  * Returns allocated string, or NULL for end of function.
  */
-
     static char_u *
 get_list_line(
     int	    c UNUSED,
     void    *cookie,
     int	    indent UNUSED,
-    int	    do_concat UNUSED)
+    getline_opt_T options UNUSED)
 {
     listitem_T **p = (listitem_T **)cookie;
     listitem_T *item = *p;
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -1867,7 +1867,7 @@ struct exarg
     int		bad_char;	// BAD_KEEP, BAD_DROP or replacement byte
     int		useridx;	// user command index
     char	*errmsg;	// returned error message
-    char_u	*(*getline)(int, void *, int, int);
+    char_u	*(*getline)(int, void *, int, getline_opt_T);
     void	*cookie;	// argument for getline()
 #ifdef FEAT_EVAL
     cstack_T	*cstack;	// condition stack for ":if" etc.
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -612,7 +612,7 @@ do_cmdline_cmd(char_u *cmd)
     int
 do_cmdline(
     char_u	*cmdline,
-    char_u	*(*fgetline)(int, void *, int, int),
+    char_u	*(*fgetline)(int, void *, int, getline_opt_T),
     void	*cookie,		// argument for fgetline()
     int		flags)
 {
@@ -638,7 +638,7 @@ do_cmdline(
     msglist_T	*private_msg_list;
 
     // "fgetline" and "cookie" passed to do_one_cmd()
-    char_u	*(*cmd_getline)(int, void *, int, int);
+    char_u	*(*cmd_getline)(int, void *, int, getline_opt_T);
     void	*cmd_cookie;
     struct loop_cookie cmd_loop_cookie;
     void	*real_cookie;
@@ -1482,9 +1482,9 @@ free_cmdlines(garray_T *gap)
  */
     int
 getline_equal(
-    char_u	*(*fgetline)(int, void *, int, int),
+    char_u	*(*fgetline)(int, void *, int, getline_opt_T),
     void	*cookie UNUSED,		// argument for fgetline()
-    char_u	*(*func)(int, void *, int, int))
+    char_u	*(*func)(int, void *, int, getline_opt_T))
 {
 #ifdef FEAT_EVAL
     char_u		*(*gp)(int, void *, int, int);
@@ -1512,7 +1512,7 @@ getline_equal(
  */
     void *
 getline_cookie(
-    char_u	*(*fgetline)(int, void *, int, int) UNUSED,
+    char_u	*(*fgetline)(int, void *, int, getline_opt_T) UNUSED,
     void	*cookie)		// argument for fgetline()
 {
 #ifdef FEAT_EVAL
@@ -1541,7 +1541,7 @@ getline_cookie(
  */
     char_u *
 getline_peek(
-    char_u	*(*fgetline)(int, void *, int, int) UNUSED,
+    char_u	*(*fgetline)(int, void *, int, getline_opt_T) UNUSED,
     void	*cookie)		// argument for fgetline()
 {
     char_u		*(*gp)(int, void *, int, int);
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -2705,12 +2705,12 @@ getexline(
     int		c,		// normally ':', NUL for ":append"
     void	*cookie UNUSED,
     int		indent,		// indent for inside conditionals
-    int		do_concat)
+    getline_opt_T options)
 {
     // When executing a register, remove ':' that's in front of each line.
     if (exec_from_reg && vpeekc() == ':')
 	(void)vgetc();
-    return getcmdline(c, 1L, indent, do_concat);
+    return getcmdline(c, 1L, indent, options);
 }
 
 /*
@@ -2725,7 +2725,7 @@ getexmodeline(
 				// :s prompt
     void	*cookie UNUSED,
     int		indent,		// indent for inside conditionals
-    int		do_concat UNUSED)
+    getline_opt_T options UNUSED)
 {
     garray_T	line_ga;
     char_u	*pend;
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -28,7 +28,7 @@ int has_completechanged(void);
 void block_autocmds(void);
 void unblock_autocmds(void);
 int is_autocmd_blocked(void);
-char_u *getnextac(int c, void *cookie, int indent, int do_concat);
+char_u *getnextac(int c, void *cookie, int indent, getline_opt_T options);
 int has_autocmd(event_T event, char_u *sfname, buf_T *buf);
 char_u *get_augroup_name(expand_T *xp, int idx);
 char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd);
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -1,10 +1,10 @@
 /* ex_docmd.c */
 void do_exmode(int improved);
 int do_cmdline_cmd(char_u *cmd);
-int do_cmdline(char_u *cmdline, char_u *(*fgetline)(int, void *, int, int), void *cookie, int flags);
-int getline_equal(char_u *(*fgetline)(int, void *, int, int), void *cookie, char_u *(*func)(int, void *, int, int));
-void *getline_cookie(char_u *(*fgetline)(int, void *, int, int), void *cookie);
-char_u *getline_peek(char_u *(*fgetline)(int, void *, int, int), void *cookie);
+int do_cmdline(char_u *cmdline, char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie, int flags);
+int getline_equal(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie, char_u *(*func)(int, void *, int, getline_opt_T));
+void *getline_cookie(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie);
+char_u *getline_peek(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie);
 char *ex_errmsg(char *msg, char_u *arg);
 int parse_command_modifiers(exarg_T *eap, char **errormsg, int skip_only);
 void undo_cmdmod(exarg_T *eap, int save_msg_scroll);
--- a/src/proto/ex_getln.pro
+++ b/src/proto/ex_getln.pro
@@ -9,8 +9,8 @@ char *get_text_locked_msg(void);
 int text_locked(void);
 int curbuf_locked(void);
 int allbuf_locked(void);
-char_u *getexline(int c, void *cookie, int indent, int do_concat);
-char_u *getexmodeline(int promptc, void *cookie, int indent, int do_concat);
+char_u *getexline(int c, void *cookie, int indent, getline_opt_T options);
+char_u *getexmodeline(int promptc, void *cookie, int indent, getline_opt_T options);
 int cmdline_overstrike(void);
 int cmdline_at_end(void);
 colnr_T cmdline_getvcol_cursor(void);
--- a/src/proto/scriptfile.pro
+++ b/src/proto/scriptfile.pro
@@ -29,13 +29,13 @@ void scriptnames_slash_adjust(void);
 char_u *get_scriptname(scid_T id);
 void free_scriptnames(void);
 void free_autoload_scriptnames(void);
-linenr_T get_sourced_lnum(char_u *(*fgetline)(int, void *, int, int), void *cookie);
-char_u *getsourceline(int c, void *cookie, int indent, int do_concat);
+linenr_T get_sourced_lnum(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie);
+char_u *getsourceline(int c, void *cookie, int indent, getline_opt_T options);
 void ex_scriptencoding(exarg_T *eap);
 void ex_scriptversion(exarg_T *eap);
 void ex_finish(exarg_T *eap);
 void do_finish(exarg_T *eap, int reanimate);
-int source_finished(char_u *(*fgetline)(int, void *, int, int), void *cookie);
+int source_finished(char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie);
 char_u *autoload_name(char_u *name);
 int script_autoload(char_u *name, int reload);
 /* vim: set ft=c : */
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -46,7 +46,7 @@ void ex_call(exarg_T *eap);
 int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv);
 void discard_pending_return(void *rettv);
 char_u *get_return_cmd(void *rettv);
-char_u *get_func_line(int c, void *cookie, int indent, int do_concat);
+char_u *get_func_line(int c, void *cookie, int indent, getline_opt_T options);
 int func_has_ended(void *cookie);
 int func_has_abort(void *cookie);
 dict_T *make_partial(dict_T *selfdict_in, typval_T *rettv);
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -1604,7 +1604,9 @@ free_autoload_scriptnames(void)
 #endif
 
     linenr_T
-get_sourced_lnum(char_u *(*fgetline)(int, void *, int, int), void *cookie)
+get_sourced_lnum(
+	char_u *(*fgetline)(int, void *, int, getline_opt_T),
+	void *cookie)
 {
     return fgetline == getsourceline
 			? ((struct source_cookie *)cookie)->sourcing_lnum
@@ -1724,7 +1726,11 @@ get_one_sourceline(struct source_cookie 
  * Return NULL for end-of-file or some error.
  */
     char_u *
-getsourceline(int c UNUSED, void *cookie, int indent UNUSED, int do_concat)
+getsourceline(
+	int c UNUSED,
+	void *cookie,
+	int indent UNUSED,
+	getline_opt_T options)
 {
     struct source_cookie *sp = (struct source_cookie *)cookie;
     char_u		*line;
@@ -1765,7 +1771,8 @@ getsourceline(int c UNUSED, void *cookie
 
     // Only concatenate lines starting with a \ when 'cpoptions' doesn't
     // contain the 'C' flag.
-    if (line != NULL && do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL)
+    if (line != NULL && options != GETLINE_NONE
+				      && vim_strchr(p_cpo, CPO_CONCAT) == NULL)
     {
 	// compensate for the one line read-ahead
 	--sp->sourcing_lnum;
@@ -1781,7 +1788,8 @@ getsourceline(int c UNUSED, void *cookie
 			      || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')
 #ifdef FEAT_EVAL
 			      || (in_vim9script()
-				       && (*p == NUL || vim9_comment_start(p)))
+				      && options == GETLINE_CONCAT_ALL
+				      && (*p == NUL || vim9_comment_start(p)))
 #endif
 			      ))
 	{
@@ -1814,7 +1822,8 @@ getsourceline(int c UNUSED, void *cookie
 		else if (!(p[0] == '"' && p[1] == '\\' && p[2] == ' ')
 #ifdef FEAT_EVAL
 			&& !(in_vim9script()
-				       && (*p == NUL || vim9_comment_start(p)))
+				&& options == GETLINE_CONCAT_ALL
+				&& (*p == NUL || vim9_comment_start(p)))
 #endif
 			)
 		    break;
@@ -1968,7 +1977,7 @@ do_finish(exarg_T *eap, int reanimate)
  */
     int
 source_finished(
-    char_u	*(*fgetline)(int, void *, int, int),
+    char_u	*(*fgetline)(int, void *, int, getline_opt_T),
     void	*cookie)
 {
     return (getline_equal(fgetline, cookie, getsourceline)
--- a/src/structs.h
+++ b/src/structs.h
@@ -1761,6 +1761,13 @@ typedef struct
 # endif
 } scriptitem_T;
 
+// type of getline() last argument
+typedef enum {
+    GETLINE_NONE,	    // do not concatenate any lines
+    GETLINE_CONCAT_CONT,    // concatenate continuation lines
+    GETLINE_CONCAT_ALL	    // concatenate continuation and Vim9 # comment lines
+} getline_opt_T;
+
 // Struct passed through eval() functions.
 // See EVALARG_EVALUATE for a fixed value with eval_flags set to EVAL_EVALUATE.
 typedef struct {
@@ -1768,7 +1775,7 @@ typedef struct {
     int		eval_break_count;   // nr of line breaks consumed
 
     // copied from exarg_T when "getline" is "getsourceline". Can be NULL.
-    char_u	*(*eval_getline)(int, void *, int, int);
+    char_u	*(*eval_getline)(int, void *, int, getline_opt_T);
     void	*eval_cookie;	    // argument for eval_getline()
 
     // used when compiling a :def function, NULL otherwise
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -1141,6 +1141,17 @@ def Test_list_vimscript()
       assert_equal(['one', 'two', 'three'], mylist)
   END
   CheckScriptSuccess(lines)
+
+  # check all lines from heredoc are kept
+  lines =<< trim END
+      # comment 1
+      two
+      # comment 3
+
+      five
+      # comment 6
+  END
+  assert_equal(['# comment 1', 'two', '# comment 3', '', 'five', '# comment 6'], lines)
 enddef
 
 if has('channel')
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -2651,7 +2651,7 @@ def_function(exarg_T *eap, char_u *name_
     static int	func_nr = 0;	    // number for nameless function
     int		paren;
     hashitem_T	*hi;
-    int		do_concat = TRUE;
+    getline_opt_T getline_options = GETLINE_CONCAT_CONT;
     linenr_T	sourcing_lnum_off;
     linenr_T	sourcing_lnum_top;
     int		is_heredoc = FALSE;
@@ -3008,9 +3008,10 @@ def_function(exarg_T *eap, char_u *name_
 	{
 	    vim_free(line_to_free);
 	    if (eap->getline == NULL)
-		theline = getcmdline(':', 0L, indent, do_concat);
+		theline = getcmdline(':', 0L, indent, getline_options);
 	    else
-		theline = eap->getline(':', eap->cookie, indent, do_concat);
+		theline = eap->getline(':', eap->cookie, indent,
+							      getline_options);
 	    line_to_free = theline;
 	}
 	if (KeyTyped)
@@ -3053,7 +3054,7 @@ def_function(exarg_T *eap, char_u *name_
 		{
 		    VIM_CLEAR(skip_until);
 		    VIM_CLEAR(heredoc_trimmed);
-		    do_concat = TRUE;
+		    getline_options = GETLINE_CONCAT_CONT;
 		    is_heredoc = FALSE;
 		}
 	    }
@@ -3178,7 +3179,7 @@ def_function(exarg_T *eap, char_u *name_
 		    skip_until = vim_strsave((char_u *)".");
 		else
 		    skip_until = vim_strnsave(p, skiptowhite(p) - p);
-		do_concat = FALSE;
+		getline_options = GETLINE_NONE;
 		is_heredoc = TRUE;
 	    }
 
@@ -3205,7 +3206,7 @@ def_function(exarg_T *eap, char_u *name_
 						 skipwhite(theline) - theline);
 		    }
 		    skip_until = vim_strnsave(p, skiptowhite(p) - p);
-		    do_concat = FALSE;
+		    getline_options = GETLINE_NONE;
 		    is_heredoc = TRUE;
 		}
 	    }
@@ -4249,7 +4250,7 @@ get_func_line(
     int	    c UNUSED,
     void    *cookie,
     int	    indent UNUSED,
-    int	    do_concat UNUSED)
+    getline_opt_T options UNUSED)
 {
     funccall_T	*fcp = (funccall_T *)cookie;
     ufunc_T	*fp = fcp->func;
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1491,
+/**/
     1490,
 /**/
     1489,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -4197,18 +4197,21 @@ exarg_getline(
 	int c UNUSED,
 	void *cookie,
 	int indent UNUSED,
-	int do_concat UNUSED)
+	getline_opt_T options UNUSED)
 {
     cctx_T  *cctx = (cctx_T *)cookie;
-
-    if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len)
-    {
-	iemsg("Heredoc got to end");
-	return NULL;
-    }
-    ++cctx->ctx_lnum;
-    return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)
-							     [cctx->ctx_lnum]);
+    char_u  *p;
+
+    for (;;)
+    {
+	if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len)
+	    return NULL;
+	++cctx->ctx_lnum;
+	p = ((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)[cctx->ctx_lnum];
+	// Comment lines result in NULL pointers, skip them.
+	if (p != NULL)
+	    return vim_strsave(p);
+    }
 }
 
 /*