[PATCH 3/4] [cmd] Fix setlocal/endlocal implementation

Ann and Jason Edmeades jason at edmeades.me.uk
Mon Oct 1 06:39:34 CDT 2012


setlocal/endlocal handling was dodgy in that if a batch program ended
with outstanding contexts, they were not being unwound, and additional
tests showed issues that called batch programs could unwind contexts
from the callee. This patch adds lots of tests for set/endlocal and
resolves the problems they highlight.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.winehq.org/pipermail/wine-patches/attachments/20121001/5447af29/attachment.html>
-------------- next part --------------
From 68d96d6c02fd0990841d2f8b6595a24b4c1e3ecb Mon Sep 17 00:00:00 2001
From: Jason Edmeades <jason at edmeades.me.uk>
Date: Sun, 30 Sep 2012 23:07:01 +0100
Subject: [PATCH 3/4] [cmd] Fix setlocal/endlocal implementation

setlocal/endlocal handling was dodgy in that if a batch program ended
with outstanding contexts, they were not being unwound, and additional
tests showed issues that called batch programs could unwind contexts
from the callee. This patch adds lots of tests for set/endlocal and
resolves the problems they highlight.
---
 programs/cmd/batch.c                     |    9 ++
 programs/cmd/builtins.c                  |   14 ++-
 programs/cmd/tests/test_builtins.cmd     |  136 ++++++++++++++++++++++++++++++
 programs/cmd/tests/test_builtins.cmd.exp |   50 ++++++++++-
 programs/cmd/wcmd.h                      |    3 +-
 5 files changed, 207 insertions(+), 5 deletions(-)

diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c
index 4926b1f..b77931d 100644
--- a/programs/cmd/batch.c
+++ b/programs/cmd/batch.c
@@ -22,6 +22,8 @@
 #include "wcmd.h"
 #include "wine/debug.h"
 
+extern struct env_stack *saved_environment;
+
 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
 
 /****************************************************************************
@@ -94,6 +96,13 @@ void WCMD_batch (WCHAR *file, WCHAR *command, BOOL called, WCHAR *startLabel, HA
   CloseHandle (h);
 
 /*
+ *  If there are outstanding setlocal's to the current context, unwind them.
+ */
+  while (saved_environment && saved_environment->batchhandle == context->h) {
+      WCMD_endlocal();
+  }
+
+/*
  *	If invoked by a CALL, we return to the context of our caller. Otherwise return
  *	to the caller's caller.
  */
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index 337502b..33782ae 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -103,7 +103,7 @@ static const WCHAR parmY[] = {'/','Y','\0'};
 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
 
 static HINSTANCE hinst;
-static struct env_stack *saved_environment;
+struct env_stack *saved_environment;
 static BOOL verify_mode = FALSE;
 
 /**************************************************************************
@@ -2085,6 +2085,9 @@ void WCMD_setlocal (const WCHAR *s) {
   struct env_stack *env_copy;
   WCHAR cwd[MAX_PATH];
 
+  /* setlocal does nothing outside of batch programs */
+  if (!context) return;
+
   /* DISABLEEXTENSIONS ignored */
 
   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
@@ -2095,10 +2098,10 @@ void WCMD_setlocal (const WCHAR *s) {
   }
 
   env = GetEnvironmentStringsW ();
-
   env_copy->strings = WCMD_dupenv (env);
   if (env_copy->strings)
   {
+    env_copy->batchhandle = context->h;
     env_copy->next = saved_environment;
     saved_environment = env_copy;
 
@@ -2125,7 +2128,12 @@ void WCMD_endlocal (void) {
   struct env_stack *temp;
   int len, n;
 
-  if (!saved_environment)
+  /* setlocal does nothing outside of batch programs */
+  if (!context) return;
+
+  /* setlocal needs a saved environment from within the same context (batch
+     program) as it was saved in                                            */
+  if (!saved_environment || saved_environment->batchhandle != context->h)
     return;
 
   /* pop the old environment from the stack */
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd
index f564aed..d89721a 100644
--- a/programs/cmd/tests/test_builtins.cmd
+++ b/programs/cmd/tests/test_builtins.cmd
@@ -1659,27 +1659,163 @@ cmd /e:oN /C tmp.cmd
 
 rem FIXME: creating file before setting envvar value to prevent parsing-time evaluation (due to EnableDelayedExpansion not being implemented/available yet)
 echo --- setlocal with corresponding endlocal
+rem %CD% does not tork on NT4 so use the following workaround
+for /d %%i in (.) do set CURDIR=%%~dpnxi
 echo @echo off> test.cmd
 echo echo %%VAR%%>> test.cmd
 echo setlocal>> test.cmd
 echo set VAR=localval>> test.cmd
+echo md foobar2>> test.cmd
+echo cd foobar2>> test.cmd
 echo echo %%VAR%%>> test.cmd
+echo for /d %%%%i in (.) do echo %%%%~dpnxi>> test.cmd
 echo endlocal>> test.cmd
 echo echo %%VAR%%>> test.cmd
+echo for /d %%%%i in (.) do echo %%%%~dpnxi>> test.cmd
 set VAR=globalval
 call test.cmd
 echo %VAR%
+for /d %%i in (.) do echo %%~dpnxi
+cd /d %curdir%
+rd foobar2
 set VAR=
 echo --- setlocal with no corresponding endlocal
 echo @echo off> test.cmd
 echo echo %%VAR%%>> test.cmd
 echo setlocal>> test.cmd
 echo set VAR=localval>> test.cmd
+echo md foobar2>> test.cmd
+echo cd foobar2>> test.cmd
 echo echo %%VAR%%>> test.cmd
+echo for /d %%%%i in (.) do echo %%%%~dpnxi>> test.cmd
 set VAR=globalval
+rem %CD% does not tork on NT4 so use the following workaround
+for /d %%i in (.) do set CURDIR=%%~dpnxi
 call test.cmd
 echo %VAR%
+for /d %%i in (.) do echo %%~dpnxi
+cd /d %curdir%
+rd foobar2
 set VAR=
+echo --- setlocal within same batch program
+set var1=one
+set var2=
+set var3=
+rem %CD% does not tork on NT4 so use the following workaround
+for /d %%i in (.) do set CURDIR=%%~dpnxi
+setlocal
+set var2=two
+mkdir foobar2
+cd foobar2
+setlocal
+set var3=three
+if "%var1%"=="one" echo Var1 ok 1
+if "%var2%"=="two" echo Var2 ok 2
+if "%var3%"=="three" echo Var3 ok 3
+for /d %%i in (.) do set curdir2=%%~dpnxi
+if "%curdir2%"=="%curdir%\foobar2" echo Directory is ok 1
+endlocal
+if "%var1%"=="one" echo Var1 ok 1
+if "%var2%"=="two" echo Var2 ok 2
+if "%var3%"=="" echo Var3 ok 3
+for /d %%i in (.) do set curdir2=%%~dpnxi
+if "%curdir2%"=="%curdir%\foobar2" echo Directory is ok 2
+endlocal
+if "%var1%"=="one" echo Var1 ok 1
+if "%var2%"=="" echo Var2 ok 2
+if "%var3%"=="" echo Var3 ok 3
+for /d %%i in (.) do set curdir2=%%~dpnxi
+if "%curdir2%"=="%curdir%" echo Directory is ok 3
+rd foobar2 /s /q
+set var1=
+
+echo --- Mismatched set and end locals
+mkdir foodir2 2>nul
+mkdir foodir3 2>nul
+mkdir foodir4 2>nul
+rem %CD% does not tork on NT4 so use the following workaround
+for /d %%i in (.) do set curdir=%%~dpnxi
+
+echo @echo off> 2set1end.cmd
+echo echo %%VAR%%>> 2set1end.cmd
+echo setlocal>> 2set1end.cmd
+echo set VAR=2set1endvalue1>> 2set1end.cmd
+echo cd ..\foodir3>> 2set1end.cmd
+echo setlocal>> 2set1end.cmd
+echo set VAR=2set1endvalue2>> 2set1end.cmd
+echo cd ..\foodir4>> 2set1end.cmd
+echo endlocal>> 2set1end.cmd
+echo echo %%VAR%%>> 2set1end.cmd
+echo for /d %%%%i in (.) do echo %%%%~dpnxi>> 2set1end.cmd
+
+echo @echo off> 1set2end.cmd
+echo echo %%VAR%%>> 1set2end.cmd
+echo setlocal>> 1set2end.cmd
+echo set VAR=1set2endvalue1>> 1set2end.cmd
+echo cd ..\foodir3>> 1set2end.cmd
+echo endlocal>> 1set2end.cmd
+echo echo %%VAR%%>> 1set2end.cmd
+echo for /d %%%%i in (.) do echo %%%%~dpnxi>> 1set2end.cmd
+echo endlocal>> 1set2end.cmd
+echo echo %%VAR%%>> 1set2end.cmd
+echo for /d %%%%i in (.) do echo %%%%~dpnxi>> 1set2end.cmd
+
+echo --- Extra setlocal in called batch
+set VAR=value1
+rem -- setlocal1 == this batch, should never be used inside a called routine
+setlocal
+set var=value2
+cd foodir2
+call %curdir%\2set1end.cmd
+echo Finished:
+echo %VAR%
+for /d %%i in (.) do echo %%~dpnxi
+endlocal
+echo %VAR%
+for /d %%i in (.) do echo %%~dpnxi
+cd /d %curdir%
+
+echo --- Extra endlocal in called batch
+set VAR=value1
+rem -- setlocal1 == this batch, should never be used inside a called routine
+setlocal
+set var=value2
+cd foodir2
+call %curdir%\1set2end.cmd
+echo Finished:
+echo %VAR%
+for /d %%i in (.) do echo %%~dpnxi
+endlocal
+echo %VAR%
+for /d %%i in (.) do echo %%~dpnxi
+cd /d %curdir%
+
+echo --- endlocal in called function rather than batch pgm is ineffective
+ at echo off
+set var=1
+set var2=1
+setlocal
+set var=2
+call :endlocalroutine
+echo %var%
+endlocal
+echo %var%
+goto :endlocalfinished
+:endlocalroutine
+echo %var%
+endlocal
+echo %var%
+setlocal
+set var2=2
+endlocal
+echo %var2%
+endlocal
+echo %var%
+echo %var2%
+goto :eof
+:endlocalfinished
+echo %var%
+
 cd .. & rd /q/s foobar
 
 echo ------------ Testing Errorlevel ------------
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp
index 435d0e6..4bab1ef 100644
--- a/programs/cmd/tests/test_builtins.cmd.exp
+++ b/programs/cmd/tests/test_builtins.cmd.exp
@@ -853,12 +853,60 @@ ErrLev: 0
 --- setlocal with corresponding endlocal
 globalval
 localval
+ at pwd@\foobar\foobar2
 globalval
+ at pwd@\foobar
 globalval
+ at pwd@\foobar
 --- setlocal with no corresponding endlocal
 globalval
 localval
- at todo_wine@globalval
+ at pwd@\foobar\foobar2
+globalval
+ at pwd@\foobar
+--- setlocal within same batch program
+Var1 ok 1
+Var2 ok 2
+Var3 ok 3
+Directory is ok 1
+Var1 ok 1
+Var2 ok 2
+Var3 ok 3
+Directory is ok 2
+Var1 ok 1
+Var2 ok 2
+Var3 ok 3
+Directory is ok 3
+--- Mismatched set and end locals
+--- Extra setlocal in called batch
+value2
+2set1endvalue1
+ at pwd@\foobar\foodir3
+Finished:
+value2
+ at pwd@\foobar\foodir2
+value1
+ at pwd@\foobar
+--- Extra endlocal in called batch
+value2
+value2
+ at pwd@\foobar\foodir2
+value2
+ at pwd@\foobar\foodir2
+Finished:
+value2
+ at pwd@\foobar\foodir2
+value1
+ at pwd@\foobar
+--- endlocal in called function rather than batch pgm is ineffective
+2
+2
+1
+2
+1
+2
+1
+1
 ------------ Testing Errorlevel ------------
 9009
 1
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h
index 451c49b..32edc2b 100644
--- a/programs/cmd/wcmd.h
+++ b/programs/cmd/wcmd.h
@@ -146,9 +146,10 @@ struct env_stack
   struct env_stack *next;
   union {
     int    stackdepth;       /* Only used for pushd and popd */
-    WCHAR   cwd;              /* Only used for set/endlocal   */
+    WCHAR   cwd;             /* Only used for set/endlocal   */
   } u;
   WCHAR *strings;
+  HANDLE batchhandle;        /* Used to ensure set/endlocals stay in scope */
 };
 
 /* Data structure to save setlocal and pushd information */
-- 
1.7.9.5


More information about the wine-patches mailing list