Jason Edmeades : cmd: Fix setlocal/endlocal implementation.

Alexandre Julliard julliard at winehq.org
Mon Oct 1 13:35:07 CDT 2012


Module: wine
Branch: master
Commit: c55cd87632bdb54c0ea506edc279ebb9118def95
URL:    http://source.winehq.org/git/wine.git/?a=commit;h=c55cd87632bdb54c0ea506edc279ebb9118def95

Author: Jason Edmeades <jason at edmeades.me.uk>
Date:   Sun Sep 30 23:07:01 2012 +0100

cmd: Fix setlocal/endlocal implementation.

---

 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 f9e51e5..f6922ad 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;
 
 /**************************************************************************
@@ -2087,6 +2087,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));
@@ -2097,10 +2100,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;
 
@@ -2127,7 +2130,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 */




More information about the wine-cvs mailing list