Skip to content

Commit 39e307e

Browse files
committed
Issue2297: Fix a stack overflow in Windows caused by -v and -vv. When python is invoked with -v or -vv under Windows, the process of importing the codec for sys.stderr causes a message to be written to stderr, which in turn causes the codec to be recursively imported. Sometimes the stack overflow exception is swallowed, other times it is not. The bug depends on the particular locale settings of the Windows machine.
Kudos to Douglas Greiman for reporting the issue and providing a patch and test case.
1 parent 8a5f8ca commit 39e307e

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

Lib/test/test_cmd_line.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,25 @@ def _spawn_python(*args):
1313
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1414

1515
def _kill_python(p):
16+
return _kill_python_and_exit_code(p)[0]
17+
18+
def _kill_python_and_exit_code(p):
1619
p.stdin.close()
1720
data = p.stdout.read()
1821
p.stdout.close()
1922
# try to cleanup the child so we don't appear to leak when running
2023
# with regrtest -R. This should be a no-op on Windows.
2124
subprocess._cleanup()
22-
return data
25+
returncode = p.wait()
26+
return data, returncode
2327

2428
class CmdLineTest(unittest.TestCase):
2529
def start_python(self, *args):
30+
return self.start_python_and_exit_code(*args)[0]
31+
32+
def start_python_and_exit_code(self, *args):
2633
p = _spawn_python(*args)
27-
return _kill_python(p)
34+
return _kill_python_and_exit_code(p)
2835

2936
def exit_code(self, *args):
3037
cmd_line = [sys.executable, '-E']
@@ -61,6 +68,17 @@ def test_version(self):
6168
version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii")
6269
self.assertTrue(self.start_python('-V').startswith(version))
6370

71+
def test_verbose(self):
72+
# -v causes imports to write to stderr. If the write to
73+
# stderr itself causes an import to happen (for the output
74+
# codec), a recursion loop can occur.
75+
data, rc = self.start_python_and_exit_code('-v')
76+
self.assertEqual(rc, 0)
77+
self.assertTrue(b'stack overflow' not in data)
78+
data, rc = self.start_python_and_exit_code('-vv')
79+
self.assertEqual(rc, 0)
80+
self.assertTrue(b'stack overflow' not in data)
81+
6482
def test_run_module(self):
6583
# Test expected operation of the '-m' switch
6684
# Switch needs an argument

Python/pythonrun.c

+17-3
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ Py_Finalize(void)
512512
/* reset file system default encoding */
513513
if (!Py_HasFileSystemDefaultEncoding) {
514514
free((char*)Py_FileSystemDefaultEncoding);
515-
Py_FileSystemDefaultEncoding = NULL;
515+
Py_FileSystemDefaultEncoding = NULL;
516516
}
517517

518518
/* XXX Still allocated:
@@ -733,6 +733,7 @@ initstdio(void)
733733
PyObject *m;
734734
PyObject *std = NULL;
735735
int status = 0, fd;
736+
PyObject * encoding_attr;
736737

737738
/* Hack to avoid a nasty recursion issue when Python is invoked
738739
in verbose mode: pre-import the Latin-1 and UTF-8 codecs */
@@ -823,6 +824,19 @@ initstdio(void)
823824
goto error;
824825
}
825826
} /* if (fd < 0) */
827+
828+
/* Same as hack above, pre-import stderr's codec to avoid recursion
829+
when import.c tries to write to stderr in verbose mode. */
830+
encoding_attr = PyObject_GetAttrString(std, "encoding");
831+
if (encoding_attr != NULL) {
832+
const char * encoding;
833+
encoding = PyUnicode_AsString(encoding_attr);
834+
if (encoding != NULL) {
835+
_PyCodec_Lookup(encoding);
836+
}
837+
}
838+
PyErr_Clear(); /* Not a fatal error if codec isn't available */
839+
826840
PySys_SetObject("__stderr__", std);
827841
PySys_SetObject("stderr", std);
828842
Py_DECREF(std);
@@ -1900,8 +1914,8 @@ PyOS_CheckStack(void)
19001914
alloca(PYOS_STACK_MARGIN * sizeof(void*));
19011915
return 0;
19021916
} __except (GetExceptionCode() == STATUS_STACK_OVERFLOW ?
1903-
EXCEPTION_EXECUTE_HANDLER :
1904-
EXCEPTION_CONTINUE_SEARCH) {
1917+
EXCEPTION_EXECUTE_HANDLER :
1918+
EXCEPTION_CONTINUE_SEARCH) {
19051919
int errcode = _resetstkoflw();
19061920
if (errcode)
19071921
{

0 commit comments

Comments
 (0)