8000 gh-125842: Fix `sys.exit(0xffff_ffff)` on Windows (#125896) · python/cpython@ad6110a · GitHub
[go: up one dir, main page]

Skip to content

Commit ad6110a

Browse files
authored
gh-125842: Fix sys.exit(0xffff_ffff) on Windows (#125896)
On Windows, `long` is a signed 32-bit integer so it can't represent `0xffff_ffff` without overflow. Windows exit codes are unsigned 32-bit integers, so if a child process exits with `-1`, it will be represented as `0xffff_ffff`. Also fix a number of other possible cases where `_Py_HandleSystemExit` could return with an exception set, leading to a `SystemError` (or fatal error in debug builds) later on during shutdown.
1 parent e545ead commit ad6110a

File tree

3 files changed

+63
-33
lines changed

3 files changed

+63
-33
lines changed

Lib/test/test_sys.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,20 @@ def test_exit(self):
206206
self.assertEqual(out, b'')
207207
self.assertEqual(err, b'')
208208

209+
# gh-125842: Windows uses 32-bit unsigned integers for exit codes
210+
# so a -1 exit code is sometimes interpreted as 0xffff_ffff.
211+
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(0xffff_ffff)')
212+
self.assertIn(rc, (-1, 0xff, 0xffff_ffff))
213+
self.assertEqual(out, b'')
214+
self.assertEqual(err, b'')
215+
216+
# Overflow results in a -1 exit code, which may be converted to 0xff
217+
# or 0xffff_ffff.
218+
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(2**128)')
219+
self.assertIn(rc, (-1, 0xff, 0xffff_ffff))
220+
self.assertEqual(out, b'')
221+
self.assertEqual(err, b'')
222+
209223
# call with integer argument
210224
with self.assertRaises(SystemExit) as cm:
211225
sys.exit(42)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a :exc:`SystemError` when :func:`sys.exit` is called with ``0xffffffff``
2+
on Windows.

Python/pythonrun.c

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,30 @@ PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
564564
return _PyRun_SimpleStringFlagsWithName(command, NULL, flags);
565565
}
566566

567+
static int
568+
parse_exit_code(PyObject *code, int *exitcode_p)
569+
{
570+
if (PyLong_Check(code)) {
571+
// gh-125842: Use a long long to avoid an overflow error when `long`
572+
// is 32-bit. We still truncate the result to an int.
573+
int exitcode = (int)PyLong_AsLongLong(code);
574+
if (exitcode == -1 && PyErr_Occurred()) {
575+
// On overflow or other error, clear the exception and use -1
576+
// as the exit code to match historical Python behavior.
577+
PyErr_Clear();
578+
*exitcode_p = -1;
579+
return 1;
580+
}
581+
*exitcode_p = exitcode;
582+
return 1;
583+
}
584+
else if (code == Py_None) {
585+
*exitcode_p = 0;
586+
return 1;
587+
}
588+
return 0;
589+
}
590+
567591
int
568592
_Py_HandleSystemExit(int *exitcode_p)
569593
{
@@ -580,50 +604,40 @@ _Py_HandleSystemExit(int *exitcode_p)
580604

581605
fflush(stdout);
582606

583-
int exitcode = 0;
584-
585607
PyObject *exc = PyErr_GetRaisedException();
586-
if (exc == NULL) {
587-
goto done;
588-
}
589-
assert(PyExceptionInstance_Check(exc));
608+
assert(exc != NULL && PyExceptionInstance_Check(exc));
590609

591-
/* The error code should be in the `code' attribute. */
592610
PyObject *code = PyObject_GetAttr(exc, &_Py_ID(code));
593-
if (code) {
611+
if (code == NULL) {
612+
// If the exception has no 'code' attribute, print the exception below
613+
PyErr_Clear();
614+
}
615+
else if (parse_exit_code(code, exitcode_p)) {
616+
Py_DECREF(code);
617+
Py_CLEAR(exc);
618+
return 1;
619+
}
620+
else {
621+
// If code is not an int or None, print it below
594622
Py_SETREF(exc, code);
595-
if (exc == Py_None) {
596-
goto done;
597-
}
598623
}
599-
/* If we failed to dig out the 'code' attribute,
600-
* just let the else clause below print the error.
601-
*/
602624

603-
if (PyLong_Check(exc)) {
604-
exitcode = (int)PyLong_AsLong(exc);
625+
PyThreadState *tstate = _PyThreadState_GET();
626+
PyObject *sys_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
627+
if (sys_stderr != NULL && sys_stderr != Py_None) {
628+
if (PyFile_WriteObject(exc, sys_stderr, Py_PRINT_RAW) < 0) {
629+
PyErr_Clear();
630+
}
605631
}
606632
else {
607-
PyThreadState *tstate = _PyThreadState_GET();
608-
PyObject *sys_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
609-
/* We clear the exception here to avoid triggering the assertion
610-
* in PyObject_Str that ensures it won't silently lose exception
611-
* details.
612-
*/
613-
PyErr_Clear();
614-
if (sys_stderr != NULL && sys_stderr != Py_None) {
615-
PyFile_WriteObject(exc, sys_stderr, Py_PRINT_RAW);
616-
} else {
617-
PyObject_Print(exc, stderr, Py_PRINT_RAW);
618-
fflush(stderr);
633+
if (PyObject_Print(exc, stderr, Py_PRINT_RAW) < 0) {
634+
PyErr_Clear();
619635
}
620-
PySys_WriteStderr("\n");
621-
exitcode = 1;
636+
fflush(stderr);
622637
}
623-
624-
done:
638+
PySys_WriteStderr("\n");
625639
Py_CLEAR(exc);
626-
*exitcode_p = exitcode;
640+
*exitcode_p = 1;
627641
return 1;
628642
}
629643

0 commit comments

Comments
 (0)
0