[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