From c55cd87632bdb54c0ea506edc279ebb9118def95 Mon Sep 17 00:00:00 2001 From: Jason Edmeades Date: Sun, 30 Sep 2012 23:07:01 +0100 Subject: [PATCH] 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 4926b1f2f91..b77931d477e 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); /**************************************************************************** @@ -93,6 +95,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 f9e51e563e4..f6922adf3a2 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 f564aedc62b..d89721a12a2 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 +@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 435d0e63a0d..4bab1ef9ee2 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 +@pwd@\foobar\foobar2 globalval +@pwd@\foobar globalval +@pwd@\foobar --- setlocal with no corresponding endlocal globalval localval -@todo_wine@globalval +@pwd@\foobar\foobar2 +globalval +@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 +@pwd@\foobar\foodir3 +Finished: +value2 +@pwd@\foobar\foodir2 +value1 +@pwd@\foobar +--- Extra endlocal in called batch +value2 +value2 +@pwd@\foobar\foodir2 +value2 +@pwd@\foobar\foodir2 +Finished: +value2 +@pwd@\foobar\foodir2 +value1 +@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 451c49b179a..32edc2bb0b7 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 */