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

Skip to content 8000

Commit 52b57ee

Browse files
[3.13] gh-125842: Fix sys.exit(0xffff_ffff) on Windows (GH-125896) (GH-125925)
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. (cherry picked from commit ad6110a) Co-authored-by: Sam Gross <colesbury@gmail.com>
1 parent 5c2696b commit 52b57ee

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
@@ -563,6 +563,30 @@ PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
563563
return _PyRun_SimpleStringFlagsWithName(command, NULL, flags);
564564
}
565565

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

580604
fflush(stdout);
581605

582-
int exitcode = 0;
583-
584606
PyObject *exc = PyErr_GetRaisedException();
585-
if (exc == NULL) {
586-
goto done;
587-
}
588-
assert(PyExceptionInstance_Check(exc));
607+
assert(exc != NULL && PyExceptionInstance_Check(exc));
589608

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

602-
if (PyLong_Check(exc)) {
603-
exitcode = (int)PyLong_AsLong(exc);
624+
PyThreadState *tstate = _PyThreadState_GET();
625+
PyObject *sys_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
626+
if (sys_stderr != NULL && sys_stderr != Py_None) {
627+
if (PyFile_WriteObject(exc, sys_stderr, Py_PRINT_RAW) < 0) {
628+
PyErr_Clear();
629+
}
604630
}
605631
else {
606-
PyThreadState *tstate = _PyThreadState_GET();
607-
PyObject *sys_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
608-
/* We clear the exception here to avoid triggering the assertion
609-
* in PyObject_Str that ensures it won't silently lose exception
610-
* details.
611-
*/
612-
PyErr_Clear();
613-
if (sys_stderr != NULL && sys_stderr != Py_None) {
614-
PyFile_WriteObject(exc, sys_stderr, Py_PRINT_RAW);
615-
} else {
616-
PyObject_Print(exc, stderr, Py_PRINT_RAW);
617-
fflush(stderr);
632+
if (PyObject_Print(exc, stderr, Py_PRINT_RAW) < 0) {
633+
PyErr_Clear();
618634
}
619-
PySys_WriteStderr("\n");
620-
exitcode = 1;
635+
fflush(stderr);
621636
}
622-
623-
done:
637+
PySys_WriteStderr("\n");
624638
Py_CLEAR(exc);
625-
*exitcode_p = exitcode;
639+
*exitcode_p = 1;
626640
return 1;
627641
}
628642

0 commit comments

Comments
 (0)
0