changeset: 106265:ed5255a61648 branch: 3.6 parent: 106261:1754722ec296 parent: 106264:269296b2a047 user: Gregory P. Smith date: Sun Jan 22 17:29:44 2017 -0800 files: Lib/subprocess.py Lib/test/test_subprocess.py Misc/NEWS description: Issue #29335: Fix subprocess.Popen.wait() when the child process has exited to a stopped instead of terminated state (ex: when under ptrace). diff -r 1754722ec296 -r ed5255a61648 Lib/subprocess.py --- a/Lib/subprocess.py Sun Jan 22 14:39:20 2017 +0800 +++ b/Lib/subprocess.py Sun Jan 22 17:29:44 2017 -0800 @@ -1329,7 +1329,8 @@ def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED, _WTERMSIG=os.WTERMSIG, _WIFEXITED=os.WIFEXITED, - _WEXITSTATUS=os.WEXITSTATUS): + _WEXITSTATUS=os.WEXITSTATUS, _WIFSTOPPED=os.WIFSTOPPED, + _WSTOPSIG=os.WSTOPSIG): """All callers to this function MUST hold self._waitpid_lock.""" # This method is called (indirectly) by __del__, so it cannot # refer to anything outside of its local scope. @@ -1337,6 +1338,8 @@ self.returncode = -_WTERMSIG(sts) elif _WIFEXITED(sts): self.returncode = _WEXITSTATUS(sts) + elif _WIFSTOPPED(sts): + self.returncode = -_WSTOPSIG(sts) else: # Should never happen raise SubprocessError("Unknown child exit status!") diff -r 1754722ec296 -r ed5255a61648 Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py Sun Jan 22 14:39:20 2017 +0800 +++ b/Lib/test/test_subprocess.py Sun Jan 22 17:29:44 2017 -0800 @@ -3,6 +3,8 @@ from test import support import subprocess import sys +import platform +import ctypes import signal import io import os @@ -2498,6 +2500,45 @@ proc.communicate(timeout=999) mock_proc_stdin.close.assert_called_once_with() + _libc_file_extensions = { + 'Linux': 'so.6', + 'Darwin': '.dylib', + } + @unittest.skipIf(platform.uname()[0] not in _libc_file_extensions, + 'Test requires a libc this code can load with ctypes.') + @unittest.skipIf(not sys.executable, 'Test requires sys.executable.') + def test_child_terminated_in_stopped_state(self): + """Test wait() behavior when waitpid returns WIFSTOPPED; issue29335.""" + PTRACE_TRACEME = 0 # From glibc and MacOS (PT_TRACE_ME). + libc_name = 'libc.' + self._libc_file_extensions[platform.uname()[0]] + libc = ctypes.CDLL(libc_name) + if not hasattr(libc, 'ptrace'): + raise unittest.SkipTest('ptrace() required.') + test_ptrace = subprocess.Popen( + [sys.executable, '-c', """if True: + import ctypes + libc = ctypes.CDLL({libc_name!r}) + libc.ptrace({PTRACE_TRACEME}, 0, 0) + """.format(libc_name=libc_name, PTRACE_TRACEME=PTRACE_TRACEME) + ]) + if test_ptrace.wait() != 0: + raise unittest.SkipTest('ptrace() failed - unable to test.') + child = subprocess.Popen( + [sys.executable, '-c', """if True: + import ctypes + libc = ctypes.CDLL({libc_name!r}) + libc.ptrace({PTRACE_TRACEME}, 0, 0) + libc.printf(ctypes.c_char_p(0xdeadbeef)) # Crash the process. + """.format(libc_name=libc_name, PTRACE_TRACEME=PTRACE_TRACEME) + ]) + try: + returncode = child.wait() + except Exception as e: + child.kill() # Clean up the hung stopped process. + raise e + self.assertNotEqual(0, returncode) + self.assertLess(returncode, 0) # signal death, likely SIGSEGV. + @unittest.skipUnless(mswindows, "Windows specific tests") class Win32ProcessTestCase(BaseTestCase): diff -r 1754722ec296 -r ed5255a61648 Misc/NEWS --- a/Misc/NEWS Sun Jan 22 14:39:20 2017 +0800 +++ b/Misc/NEWS Sun Jan 22 17:29:44 2017 -0800 @@ -47,6 +47,9 @@ Library ------- +- Issue #29335: Fix subprocess.Popen.wait() when the child process has + exited to a stopped instead of terminated state (ex: when under ptrace). + - Issue #29290: Fix a regression in argparse that help messages would wrap at non-breaking spaces.