Skip to content

Commit 483422f

Browse files
authored
bpo-34044: subprocess.Popen copies startupinfo (GH-8090)
subprocess.Popen now copies the startupinfo argument to leave it unchanged: it will modify the copy, so that the same STARTUPINFO object can be used multiple times. Add subprocess.STARTUPINFO.copy() method.
1 parent 09bb918 commit 483422f

File tree

3 files changed

+47
-0
lines changed

3 files changed

+47
-0
lines changed

Lib/subprocess.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ def __init__(self, *, dwFlags=0, hStdInput=None, hStdOutput=None,
135135
self.hStdError = hStdError
136136
self.wShowWindow = wShowWindow
137137
self.lpAttributeList = lpAttributeList or {"handle_list": []}
138+
139+
def copy(self):
140+
attr_list = self.lpAttributeList.copy()
141+
if 'handle_list' in attr_list:
142+
attr_list['handle_list'] = list(attr_list['handle_list'])
143+
144+
return STARTUPINFO(dwFlags=self.dwFlags,
145+
hStdInput=self.hStdInput,
146+
hStdOutput=self.hStdOutput,
147+
hStdError=self.hStdError,
148+
wShowWindow=self.wShowWindow,
149+
lpAttributeList=attr_list)
150+
138151
else:
139152
import _posixsubprocess
140153
import select
@@ -1102,6 +1115,10 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
11021115
# Process startup details
11031116
if startupinfo is None:
11041117
startupinfo = STARTUPINFO()
1118+
else:
1119+
# bpo-34044: Copy STARTUPINFO since it is modified above,
1120+
# so the caller can reuse it multiple times.
1121+
startupinfo = startupinfo.copy()
11051122

11061123
use_std_handles = -1 not in (p2cread, c2pwrite, errwrite)
11071124
if use_std_handles:

Lib/test/test_subprocess.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,6 +2822,33 @@ def test_startupinfo_keywords(self):
28222822
subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
28232823
startupinfo=startupinfo)
28242824

2825+
def test_startupinfo_copy(self):
2826+
# bpo-34044: Popen must not modify input STARTUPINFO structure
2827+
startupinfo = subprocess.STARTUPINFO()
2828+
startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
2829+
startupinfo.wShowWindow = subprocess.SW_HIDE
2830+
2831+
# Call Popen() twice with the same startupinfo object to make sure
2832+
# that it's not modified
2833+
for _ in range(2):
2834+
cmd = [sys.executable, "-c", "pass"]
2835+
with open(os.devnull, 'w') as null:
2836+
proc = subprocess.Popen(cmd,
2837+
stdout=null,
2838+
stderr=subprocess.STDOUT,
2839+
startupinfo=startupinfo)
2840+
with proc:
2841+
proc.communicate()
2842+
self.assertEqual(proc.returncode, 0)
2843+
2844+
self.assertEqual(startupinfo.dwFlags,
2845+
subprocess.STARTF_USESHOWWINDOW)
2846+
self.assertIsNone(startupinfo.hStdInput)
2847+
self.assertIsNone(startupinfo.hStdOutput)
2848+
self.assertIsNone(startupinfo.hStdError)
2849+
self.assertEqual(startupinfo.wShowWindow, subprocess.SW_HIDE)
2850+
self.assertEqual(startupinfo.lpAttributeList, {"handle_list": []})
2851+
28252852
def test_creationflags(self):
28262853
# creationflags argument
28272854
CREATE_NEW_CONSOLE = 16
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``subprocess.Popen`` now copies the *startupinfo* argument to leave it
2+
unchanged: it will modify the copy, so that the same ``STARTUPINFO`` object can
3+
be used multiple times.

0 commit comments

Comments
 (0)