Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,20 @@ def test_exit(self):
self.assertEqual(out, b'')
self.assertEqual(err, b'')

# gh-125842: Windows uses 32-bit unsigned integers for exit codes
# so a -1 exit code is sometimes interpreted as 0xffff_ffff.
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(0xffff_ffff)')
self.assertIn(rc, (-1, 0xff, 0xffff_ffff))
self.assertEqual(out, b'')
self.assertEqual(err, b'')

# Overflow results in a -1 exit code, which may be converted to 0xff
# or 0xffff_ffff.
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(2**128)')
self.assertIn(rc, (-1, 0xff, 0xffff_ffff))
self.assertEqual(out, b'')
self.assertEqual(err, b'')

# call with integer argument
with self.assertRaises(SystemExit) as cm:
sys.exit(42)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a :exc:`SystemError` when :func:`sys.exit` is called with ``0xffffffff``
on Windows.
83 changes: 48 additions & 35 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,30 @@ PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
return _PyRun_SimpleStringFlagsWithName(command, NULL, flags);
}

static int
parse_exit_code(PyObject *code, int *exitcode_p)
{
if (PyLong_Check(code)) {
// gh-125842: Use a long long to avoid an overflow error when `long`
// is 32-bit. We still truncate the result to an int.
int exitcode = (int)PyLong_AsLongLong(code);
if (exitcode == -1 && PyErr_Occurred()) {
// On overflow or other error, clear the exception and use -1
// as the exit code to match historical Python behavior.
PyErr_Clear();
*exitcode_p = -1;
return 1;
}
*exitcode_p = exitcode;
return 1;
}
else if (code == Py_None) {
*exitcode_p = 0;
return 1;
}
return 0;
}

int
_Py_HandleSystemExit(int *exitcode_p)
{
Expand All @@ -580,50 +604,39 @@ _Py_HandleSystemExit(int *exitcode_p)

fflush(stdout);

int exitcode = 0;

PyObject *exc = PyErr_GetRaisedException();
if (exc == NULL) {
goto done;
}
assert(PyExceptionInstance_Check(exc));
assert(exc != NULL && PyExceptionInstance_Check(exc));

/* The error code should be in the `code' attribute. */
PyObject *code = PyObject_GetAttr(exc, &_Py_ID(code));
if (code) {
Py_SETREF(exc, code);
if (exc == Py_None) {
goto done;
}
if (code == NULL) {
// If the exception has no 'code' attribute, print the exception below
PyErr_Clear();
}
/* If we failed to dig out the 'code' attribute,
* just let the else clause below print the error.
*/

if (PyLong_Check(exc)) {
exitcode = (int)PyLong_AsLong(exc);
else if (parse_exit_code(code, exitcode_p)) {
Py_DECREF(code);
Py_CLEAR(exc);
return 1;
}
else {
PyThreadState *tstate = _PyThreadState_GET();
PyObject *sys_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
/* We clear the exception here to avoid triggering the assertion
* in PyObject_Str that ensures it won't silently lose exception
* details.
*/
PyErr_Clear();
if (sys_stderr != NULL && sys_stderr != Py_None) {
PyFile_WriteObject(exc, sys_stderr, Py_PRINT_RAW);
} else {
PyObject_Print(exc, stderr, Py_PRINT_RAW);
fflush(stderr);
}
PySys_WriteStderr("\n");
exitcode = 1;
// If code is not an int or None, print it below
Py_SETREF(exc, code);
}

done:
PyThreadState *tstate = _PyThreadState_GET();
PyObject *sys_stderr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
if (sys_stderr != NULL && sys_stderr != Py_None) {
if (PyFile_WriteObject(exc, sys_stderr, Py_PRINT_RAW) < 0) {
PyErr_Clear();
}
} else {
if (PyObject_Print(exc, stderr, Py_PRINT_RAW) < 0) {
PyErr_Clear();
}
fflush(stderr);
}
PySys_WriteStderr("\n");
Py_CLEAR(exc);
*exitcode_p = exitcode;
*exitcode_p = 1;
return 1;
}

Expand Down
Loading