patch 8.2.2332: Vim9: missing :endif not reported when using :windo

Problem:    Vim9: missing :endif not reported when using :windo.
Solution:   Pass a getline function to do_cmdline(). (closes #7650)
diff --git a/src/scriptfile.c b/src/scriptfile.c
index adc5caf..c8a23d5 100644
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -1019,30 +1019,6 @@
 /*
  * ":source" and associated commands.
  */
-/*
- * Structure used to store info for each sourced file.
- * It is shared between do_source() and getsourceline().
- * This is required, because it needs to be handed to do_cmdline() and
- * sourcing can be done recursively.
- */
-struct source_cookie
-{
-    FILE	*fp;		// opened file for sourcing
-    char_u	*nextline;	// if not NULL: line that was read ahead
-    linenr_T	sourcing_lnum;	// line number of the source file
-    int		finished;	// ":finish" used
-#ifdef USE_CRNL
-    int		fileformat;	// EOL_UNKNOWN, EOL_UNIX or EOL_DOS
-    int		error;		// TRUE if LF found after CR-LF
-#endif
-#ifdef FEAT_EVAL
-    linenr_T	breakpoint;	// next line with breakpoint or zero
-    char_u	*fname;		// name of sourced file
-    int		dbg_tick;	// debug_tick when breakpoint was set
-    int		level;		// top nesting level of sourced file
-#endif
-    vimconv_T	conv;		// type of conversion
-};
 
 #ifdef FEAT_EVAL
 /*
@@ -1051,7 +1027,7 @@
     linenr_T *
 source_breakpoint(void *cookie)
 {
-    return &((struct source_cookie *)cookie)->breakpoint;
+    return &((source_cookie_T *)cookie)->breakpoint;
 }
 
 /*
@@ -1060,7 +1036,7 @@
     int *
 source_dbg_tick(void *cookie)
 {
-    return &((struct source_cookie *)cookie)->dbg_tick;
+    return &((source_cookie_T *)cookie)->dbg_tick;
 }
 
 /*
@@ -1069,7 +1045,7 @@
     int
 source_level(void *cookie)
 {
-    return ((struct source_cookie *)cookie)->level;
+    return ((source_cookie_T *)cookie)->level;
 }
 
 /*
@@ -1079,7 +1055,7 @@
     char_u *
 source_nextline(void *cookie)
 {
-    return ((struct source_cookie *)cookie)->nextline;
+    return ((source_cookie_T *)cookie)->nextline;
 }
 #endif
 
@@ -1130,7 +1106,7 @@
     int		is_vimrc,	    // DOSO_ value
     int		*ret_sid UNUSED)
 {
-    struct source_cookie    cookie;
+    source_cookie_T	    cookie;
     char_u		    *p;
     char_u		    *fname_exp;
     char_u		    *firstline = NULL;
@@ -1613,12 +1589,12 @@
 	void *cookie)
 {
     return fgetline == getsourceline
-			? ((struct source_cookie *)cookie)->sourcing_lnum
+			? ((source_cookie_T *)cookie)->sourcing_lnum
 			: SOURCING_LNUM;
 }
 
     static char_u *
-get_one_sourceline(struct source_cookie *sp)
+get_one_sourceline(source_cookie_T *sp)
 {
     garray_T		ga;
     int			len;
@@ -1736,7 +1712,7 @@
 	int indent UNUSED,
 	getline_opt_T options)
 {
-    struct source_cookie *sp = (struct source_cookie *)cookie;
+    source_cookie_T	*sp = (source_cookie_T *)cookie;
     char_u		*line;
     char_u		*p;
     int			do_vim9_all = in_vim9script()
@@ -1761,8 +1737,8 @@
     SOURCING_LNUM = sp->sourcing_lnum + 1;
 
     // Get current line.  If there is a read-ahead line, use it, otherwise get
-    // one now.
-    if (sp->finished)
+    // one now.  "fp" is NULL if actually using a string.
+    if (sp->finished || sp->fp == NULL)
 	line = NULL;
     else if (sp->nextline == NULL)
 	line = get_one_sourceline(sp);
@@ -1880,8 +1856,8 @@
     void
 ex_scriptencoding(exarg_T *eap)
 {
-    struct source_cookie	*sp;
-    char_u			*name;
+    source_cookie_T	*sp;
+    char_u		*name;
 
     if (!getline_equal(eap->getline, eap->cookie, getsourceline))
     {
@@ -1899,7 +1875,7 @@
 	name = eap->arg;
 
     // Setup for conversion from the specified encoding to 'encoding'.
-    sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie);
+    sp = (source_cookie_T *)getline_cookie(eap->getline, eap->cookie);
     convert_setup(&sp->conv, name, p_enc);
 
     if (name != eap->arg)
@@ -1963,7 +1939,7 @@
     int		idx;
 
     if (reanimate)
-	((struct source_cookie *)getline_cookie(eap->getline,
+	((source_cookie_T *)getline_cookie(eap->getline,
 					      eap->cookie))->finished = FALSE;
 
     // Cleanup (and inactivate) conditionals, but stop when a try conditional
@@ -1977,7 +1953,7 @@
 	report_make_pending(CSTP_FINISH, NULL);
     }
     else
-	((struct source_cookie *)getline_cookie(eap->getline,
+	((source_cookie_T *)getline_cookie(eap->getline,
 					       eap->cookie))->finished = TRUE;
 }
 
@@ -1993,7 +1969,7 @@
     void	*cookie)
 {
     return (getline_equal(fgetline, cookie, getsourceline)
-	    && ((struct source_cookie *)getline_cookie(
+	    && ((source_cookie_T *)getline_cookie(
 						fgetline, cookie))->finished);
 }
 
diff --git a/src/structs.h b/src/structs.h
index c47e291..d6bf667 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4300,6 +4300,32 @@
     int		sa_wrapped;	// search wrapped around
 } searchit_arg_T;
 
+/*
+ * Cookie used by getsourceline().
+ */
+/*
+ * Cookie used to store info for each sourced file.
+ * It is shared between do_source() and getsourceline().
+ * This is passed to do_cmdline().
+ */
+typedef struct {
+    FILE	*fp;		// opened file for sourcing
+    char_u	*nextline;	// if not NULL: line that was read ahead
+    linenr_T	sourcing_lnum;	// line number of the source file
+    int		finished;	// ":finish" used
+#ifdef USE_CRNL
+    int		fileformat;	// EOL_UNKNOWN, EOL_UNIX or EOL_DOS
+    int		error;		// TRUE if LF found after CR-LF
+#endif
+#ifdef FEAT_EVAL
+    linenr_T	breakpoint;	// next line with breakpoint or zero
+    char_u	*fname;		// name of sourced file
+    int		dbg_tick;	// debug_tick when breakpoint was set
+    int		level;		// top nesting level of sourced file
+#endif
+    vimconv_T	conv;		// type of conversion
+} source_cookie_T;
+
 
 #define WRITEBUFSIZE	8192	// size of normal write buffer
 
diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim
index f5cb1c8..5d66966 100644
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -921,4 +921,11 @@
   close
 enddef
 
+def Test_windo_missing_endif()
+  var lines =<< trim END
+      windo if 1
+  END
+  CheckDefExecFailure(lines, 'E171:', 1)
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/version.c b/src/version.c
index f063d7b..464154f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2332,
+/**/
     2331,
 /**/
     2330,
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 378b104..be06d89 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1382,9 +1382,18 @@
 	    // execute Ex command line
 	    case ISN_EXEC:
 		{
+		    source_cookie_T cookie;
+
 		    SOURCING_LNUM = iptr->isn_lnum;
-		    do_cmdline_cmd(iptr->isn_arg.string);
-		    if (did_emsg)
+		    // Pass getsourceline to get an error for a missing ":end"
+		    // command.
+		    CLEAR_FIELD(cookie);
+		    cookie.sourcing_lnum = iptr->isn_lnum - 1;
+		    if (do_cmdline(iptr->isn_arg.string,
+				getsourceline, &cookie,
+				   DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED)
+									== FAIL
+				|| did_emsg)
 			goto on_error;
 		}
 		break;