Skip to content

Commit c34cda2

Browse files
committed
gh-125842: Fix sys.exit(0xffff_ffff) on Windows
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 6f26d49 commit c34cda2

File tree

3 files changed

+62
-35
lines changed

3 files changed

+62
-35
lines changed

Lib/test/test_sys.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,18 @@ 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 4294967295.
211+
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(4294967295)')
212+
self.assertIn(rc, (-1, 255, 4294967295))
213+
self.assertEqual(out, b'')
214+
self.assertEqual(err, b'')
215+
216+
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(2**128)')
217+
self.assertIn(rc, (-1, 255))
218+
self.assertEqual(out, b'')
219+
self.assertEqual(err, b'')
220+
209221
# call with integer argument
210222
with self.assertRaises(SystemExit) as cm:
211223
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: 48 additions & 35 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,39 @@ _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) {
594-
Py_SETREF(exc, code);
595-
if (exc == Py_None) {
596-
goto done;
597-
}
611+
if (code == NULL) {
612+
// If the exception has no 'code' attribute, print the exception below
613+
PyErr_Clear();
598614
}
599-
/* If we failed to dig out the 'code' attribute,
600-
* just let the else clause below print the error.
601-
*/
602-
603-
if (PyLong_Check(exc)) {
604-
exitcode = (int)PyLong_AsLong(exc);
615+
else if (parse_exit_code(code, exitcode_p)) {
616+
Py_DECREF(code);
617+
Py_CLEAR(exc);
618+
return 1;
605619
}
606620
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);
619-
}
620-
PySys_WriteStderr("\n");
621-
exitcode = 1;
621+
// If code is not an int or None, print it below
622+
Py_SETREF(exc, code);
622623
}
623624

624-
done:
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+
}
631+
} else {
632+
if (PyObject_Print(exc, stderr, Py_PRINT_RAW) < 0) {
633+
PyErr_Clear();
634+
}
635+
fflush(stderr);
636+
}
637+
PySys_WriteStderr("\n");
625638
Py_CLEAR(exc);
626-
*exitcode_p = exitcode;
639+
*exitcode_p = 1;
627640
return 1;
628641
}
629642

0 commit comments

Comments
 (0)