diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index ecf62fb6391b1b..9fb22ee5b279a5 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -357,8 +357,7 @@ The following exceptions are the exceptions that are usually raised. the built-in constant. -.. exception:: OSError([arg]) - OSError(errno, strerror[, filename[, winerror[, filename2]]]) +.. exception:: OSError([[errno,] strerror,] /, filename=None, winerror=None, filename2=None) .. index:: pair: module; errno @@ -366,11 +365,24 @@ The following exceptions are the exceptions that are usually raised. error, including I/O failures such as "file not found" or "disk full" (not for illegal argument types or other incidental errors). - The second form of the constructor sets the corresponding attributes, - described below. The attributes default to :const:`None` if not - specified. For backwards compatibility, if three arguments are passed, - the :attr:`~BaseException.args` attribute contains only a 2-tuple - of the first two constructor arguments. + The constructor arguments set the corresponding attributes, described below. + If *errno* is omitted, + it defaults to the error code which corresponds to the exception class, + for the subclasses listed in `OS exceptions`_ below, + and to ``None`` for :exc:`OSError` itself. + If *strerror* is omitted, + it is derived from *winerror* on Windows when that was given, + and from the resulting :attr:`.errno` otherwise. + The remaining attributes default to ``None``. + + If *filename*, *winerror* or *filename2* is given, + the :attr:`~BaseException.args` attribute is set to + ``(errno, strerror, filename, winerror, filename2)``, + truncated after the last of the three which was given, + with omitted values replaced by ``None``. + Otherwise it contains the arguments as passed. + Either way ``type(exc)(*exc.args)`` reproduces the exception, + which is how it is pickled. The constructor often actually returns a subclass of :exc:`OSError`, as described in `OS exceptions`_ below. The particular subclass depends on @@ -380,7 +392,9 @@ The following exceptions are the exceptions that are usually raised. .. attribute:: errno - A numeric error code from the C variable :c:data:`errno`. + A numeric error code from the C variable :c:data:`errno`, + or the :attr:`default_errno` of the exception class + when the constructor was called without one. .. attribute:: winerror @@ -423,6 +437,11 @@ The following exceptions are the exceptions that are usually raised. :term:`filesystem encoding and error handler`. Also, the *filename2* constructor argument and attribute was added. + .. versionchanged:: next + *errno* and *strerror* can now be omitted. + *filename*, *winerror* and *filename2* can be passed by keyword. + The :attr:`~BaseException.args` attribute is no longer truncated. + .. exception:: OverflowError @@ -737,6 +756,20 @@ OS exceptions The following exceptions are subclasses of :exc:`OSError`, they get raised depending on the system error code. +Each of them corresponds to one or more :mod:`errno` values, +and defines the first of them as a class attribute: + +.. attribute:: OSError.default_errno + + The error code which corresponds to the exception class, + used as :attr:`~OSError.errno` when the *errno* argument is omitted. + It is not defined by :exc:`OSError` itself, + nor by :exc:`ConnectionError`, + which correspond to no single error code. + A user-defined subclass may define it + to give its instances a default :attr:`~OSError.errno` too. + + .. versionadded:: next .. exception:: BlockingIOError @@ -754,6 +787,12 @@ depending on the system error code. before it blocked. This attribute is available when using the buffered I/O classes from the :mod:`io` module. + It is set by passing the *characters_written* keyword argument + or the third positional argument. + + .. versionchanged:: next + Added the *characters_written* keyword argument. + .. exception:: ChildProcessError Raised when an operation on a child process failed. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index a1a8415482b97a..18cbcabc16acf3 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -85,6 +85,23 @@ Other language changes libraries. (Contributed by Serhiy Storchaka in :gh:`78959`.) +* The :exc:`OSError` constructor no longer requires the *errno* and + *strerror* arguments. + If *errno* is omitted, it defaults to the error code which corresponds + to the exception class, so that ``FileNotFoundError()`` works both with + code which tests the :attr:`~OSError.errno` attribute and with code + which uses :func:`isinstance`. + If *strerror* is omitted, it is derived from the resulting + :attr:`~OSError.errno`, or on Windows from :attr:`~OSError.winerror` + if that was given. + The *filename*, *winerror* and *filename2* arguments can now be passed + by keyword, as can *characters_written* for :exc:`BlockingIOError`:: + + >>> str(FileNotFoundError(filename='cfg.ini')) + "[Errno 2] No such file or directory: 'cfg.ini'" + + (Contributed by Serhiy Storchaka in :gh:`109714`.) + * :ref:`Frame objects ` now support :mod:`weak references `. This allows associating extra data with active frames, for example in debuggers, without keeping the frames (and everything @@ -845,6 +862,15 @@ that may require changes to your code. :exc:`TypeError`. (Contributed by Serhiy Storchaka in :gh:`152587`.) +* The :attr:`~BaseException.args` attribute of :exc:`OSError` is no longer + truncated to two items when a file name is given, so that + ``OSError(2, 'No such file or directory', 'cfg.ini').args`` is now the + whole 3-tuple. This makes an exception which carries a file name + survive pickling. + Passing more than five positional arguments to the constructor now + raises :exc:`TypeError` instead of being silently ignored. + (Contributed by Serhiy Storchaka in :gh:`109714`.) + * On Windows, seeking a pipe now fails instead of silently appearing to succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`, and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence, diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h index c1f9d71e40077c..9ba3f0695ce22f 100644 --- a/Include/internal/pycore_pyerrors.h +++ b/Include/internal/pycore_pyerrors.h @@ -136,6 +136,12 @@ PyAPI_FUNC(void) _PyErr_SetString( PyObject *exception, const char *string); +#ifdef MS_WINDOWS +/* Return the message for a Windows error code as a new reference, + or NULL with an exception set. */ +extern PyObject* _PyErr_WindowsErrorMessage(unsigned long err); +#endif + /* * Set an exception with the error message decoded from the current locale * encoding (LC_CTYPE). diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py index 51ac41e33ac17a..c15fe80bf9d594 100644 --- a/Lib/test/test_capi/test_exceptions.py +++ b/Lib/test/test_capi/test_exceptions.py @@ -228,7 +228,7 @@ def test_set_object(self): # is superclass, so does not wrap with self.assertRaises(PermissionError) as e: _testcapi.exc_set_object(OSError, PermissionError(24)) - self.assertEqual(e.exception.args, (24,)) + self.assertEqual(e.exception.args, (errno.EACCES, 24)) class Meta(type): def __subclasscheck__(cls, sub): @@ -305,7 +305,7 @@ def test_setfromerrnowithfilename(self): with self.assertRaises(FileNotFoundError) as e: setfromerrnowithfilename(ENOENT, OSError, b'file') self.assertEqual(e.exception.args, - (ENOENT, 'No such file or directory')) + (ENOENT, 'No such file or directory', 'file')) self.assertEqual(e.exception.errno, ENOENT) self.assertEqual(e.exception.filename, 'file') @@ -325,7 +325,7 @@ def test_setfromerrnowithfilename(self): with self.assertRaises(OSError) as e: setfromerrnowithfilename(0, OSError, b'file') - self.assertEqual(e.exception.args, (0, 'Error')) + self.assertEqual(e.exception.args, (0, 'Error', 'file')) self.assertEqual(e.exception.errno, 0) self.assertEqual(e.exception.filename, 'file') diff --git a/Lib/test/test_concurrent_futures/test_as_completed.py b/Lib/test/test_concurrent_futures/test_as_completed.py index 31c7bb3ebd872c..a071c923a239e1 100644 --- a/Lib/test/test_concurrent_futures/test_as_completed.py +++ b/Lib/test/test_concurrent_futures/test_as_completed.py @@ -108,7 +108,7 @@ def test_correct_timeout_exception_msg(self): with self.assertRaises(futures.TimeoutError) as cm: list(futures.as_completed(futures_list, timeout=0)) - self.assertEqual(str(cm.exception), '2 (of 4) futures unfinished') + self.assertIn('2 (of 4) futures unfinished', str(cm.exception)) create_executor_tests(globals(), AsCompletedTests) diff --git a/Lib/test/test_exception_hierarchy.py b/Lib/test/test_exception_hierarchy.py index 3318fa8e7746f7..5fabad70c59e69 100644 --- a/Lib/test/test_exception_hierarchy.py +++ b/Lib/test/test_exception_hierarchy.py @@ -95,6 +95,22 @@ def test_errno_mapping(self): e = OSError(errcode, "Some message") self.assertIs(type(e), OSError, repr(e)) + def _defaults(self): + # The first errno listed for a class is the one it defaults to. + defaults = {} + for errcode, exc in self._map.items(): + defaults.setdefault(exc, errcode) + return defaults + + def test_default_errno(self): + for exc, errcode in self._defaults().items(): + with self.subTest(exc=exc.__name__): + self.assertEqual(exc.default_errno, errcode) + e = exc() + self.assertEqual(e.errno, errcode) + self.assertEqual(e.strerror, os.strerror(errcode)) + self.assertEqual(e.args, (errcode, os.strerror(errcode))) + def test_try_except(self): filename = "some_hopefully_non_existing_file" @@ -136,6 +152,80 @@ def test_posix_error(self): if os.name == "nt": self.assertEqual(e.winerror, None) + def test_strerror_derived_from_errno(self): + e = FileNotFoundError() + self.assertEqual(e.errno, errno.ENOENT) + self.assertEqual(e.strerror, os.strerror(errno.ENOENT)) + # an explicit strerror wins over the one derived from errno + e = FileNotFoundError('not found') + self.assertEqual(e.errno, errno.ENOENT) + self.assertEqual(e.strerror, 'not found') + self.assertEqual(e.args, (errno.ENOENT, 'not found')) + # ... including an explicit None + e = FileNotFoundError(errno.ENOENT, None) + self.assertIsNone(e.strerror) + + @unittest.skipUnless(os.name == "nt", "Windows-specific test") + def test_strerror_derived_from_winerror(self): + # the message of the Windows error code is more specific than the one + # of the errno it is translated to + import ctypes + e = OSError(filename="foo.txt", winerror=183) + self.assertEqual(e.errno, EEXIST) + expected = ctypes.WinError(183).strerror.rstrip(" .") + self.assertEqual(e.strerror, expected) + # a code which the system has no message for + e = OSError(winerror=99999) + self.assertEqual(e.errno, errno.EINVAL) + self.assertEqual(e.strerror, "Windows Error 0x%x" % 99999) + + def test_strerror_not_derived_from_bogus_errno(self): + for code in 2**31, -2**31-1, 2**1000, -2**1000, 'x': + with self.subTest(default_errno=code): + cls = type('E', (OSError,), {'default_errno': code}) + e = cls() + self.assertEqual(e.errno, code) + self.assertIsNone(e.strerror) + + @unittest.skipUnless(os.name == "nt", "Windows-specific test") + def test_strerror_not_derived_from_bogus_winerror(self): + # a winerror out of the C long range is rejected + for code in 2**31, -2**31-1, 2**1000, -2**1000: + with self.subTest(winerror=code): + self.assertRaises(OverflowError, OSError, winerror=code) + # a winerror which is not an integer is not translated at all + e = OSError(winerror='x') + self.assertIsNone(e.errno) + self.assertIsNone(e.strerror) + self.assertEqual(e.winerror, 'x') + + def test_keyword_arguments(self): + e = FileNotFoundError(filename='foo.txt') + self.assertEqual(e.errno, errno.ENOENT) + self.assertEqual(e.strerror, os.strerror(errno.ENOENT)) + self.assertEqual(e.filename, 'foo.txt') + self.assertIsNone(e.filename2) + + e = OSError('cannot open', filename='foo.txt', filename2='bar.txt') + self.assertIsNone(e.errno) + self.assertEqual(e.strerror, 'cannot open') + self.assertEqual(e.filename, 'foo.txt') + self.assertEqual(e.filename2, 'bar.txt') + + e = OSError(EEXIST, 'exists', filename='foo.txt') + self.assertEqual(e.errno, EEXIST) + self.assertEqual(e.filename, 'foo.txt') + + def test_keyword_argument_errors(self): + # errno and strerror are positional-only + self.assertRaises(TypeError, OSError, errno=EEXIST) + self.assertRaises(TypeError, OSError, strerror='exists') + # and cannot be given twice + self.assertRaises(TypeError, OSError, + EEXIST, 'exists', 'foo.txt', filename='bar.txt') + # more than five arguments + self.assertRaises(TypeError, OSError, 1, 2, 3, 4, 5, 6) + @unittest.skipUnless(os.name == "nt", "Windows-specific test") def test_errno_translation(self): # ERROR_ALREADY_EXISTS (183) -> EEXIST @@ -145,6 +235,13 @@ def test_errno_translation(self): self.assertEqual(e.args[0], EEXIST) self.assertEqual(e.strerror, "File already exists") self.assertEqual(e.filename, "foo.txt") + # winerror can also be given by keyword + e = OSError("File already exists", filename="foo.txt", winerror=183) + self.assertEqual(e.winerror, 183) + self.assertEqual(e.errno, EEXIST) + self.assertEqual(e.args, (EEXIST, "File already exists", "foo.txt", 183)) + self.assertEqual(e.strerror, "File already exists") + self.assertEqual(e.filename, "foo.txt") def test_blockingioerror(self): args = ("a", "b", "c", "d", "e") @@ -162,6 +259,26 @@ def test_blockingioerror(self): with self.assertRaises(AttributeError): e.characters_written + # characters_written can also be given by keyword + e = BlockingIOError("would block", characters_written=3) + self.assertEqual(e.strerror, "would block") + self.assertEqual(e.characters_written, 3) + # including when the class is chosen by errno + e = OSError(errno.EAGAIN, "would block", characters_written=3) + self.assertIs(type(e), BlockingIOError) + self.assertEqual(e.characters_written, 3) + # but only for BlockingIOError + for cls in OSError, FileNotFoundError: + with self.subTest(cls=cls.__name__): + self.assertRaises(TypeError, cls, characters_written=3) + # and not together with a file name, which it is an alternative to + self.assertRaises(TypeError, BlockingIOError, + filename="foo.txt", characters_written=3) + self.assertRaises(TypeError, BlockingIOError, + errno.EAGAIN, "would block", 3, characters_written=3) + # and it is keyword-only + self.assertRaises(TypeError, BlockingIOError, 1, 2, 3, 4, 5, 6) + class ExplicitSubclassingTest(unittest.TestCase): diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index c34cf44d722456..8231b3b724fcb7 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -464,27 +464,65 @@ def testAttributes(self): {'args' : ('foo', 1)}), (SystemExit, ('foo',), {}, {'args' : ('foo',), 'code' : 'foo'}), + (OSError, (), {}, + {'args' : (), 'filename' : None, 'filename2' : None, + 'errno' : None, 'strerror' : None}), + # corresponds to no single errno, so it gets no default either + (ConnectionError, (), {}, + {'args' : (), 'filename' : None, 'filename2' : None, + 'errno' : None, 'strerror' : None}), (OSError, ('foo',), {}, {'args' : ('foo',), 'filename' : None, 'filename2' : None, - 'errno' : None, 'strerror' : None}), + 'errno' : None, 'strerror' : 'foo'}), (OSError, ('foo', 'bar'), {}, {'args' : ('foo', 'bar'), 'filename' : None, 'filename2' : None, 'errno' : 'foo', 'strerror' : 'bar'}), (OSError, ('foo', 'bar', 'baz'), {}, - {'args' : ('foo', 'bar'), + {'args' : ('foo', 'bar', 'baz'), 'filename' : 'baz', 'filename2' : None, 'errno' : 'foo', 'strerror' : 'bar'}), (OSError, ('foo', 'bar', 'baz', None, 'quux'), {}, - {'args' : ('foo', 'bar'), 'filename' : 'baz', 'filename2': 'quux'}), + {'args' : ('foo', 'bar', 'baz', None, 'quux'), + 'filename' : 'baz', 'filename2': 'quux'}), (OSError, ('errnoStr', 'strErrorStr', 'filenameStr'), {}, - {'args' : ('errnoStr', 'strErrorStr'), + {'args' : ('errnoStr', 'strErrorStr', 'filenameStr'), 'strerror' : 'strErrorStr', 'errno' : 'errnoStr', 'filename' : 'filenameStr'}), (OSError, (1, 'strErrorStr', 'filenameStr'), {}, - {'args' : (1, 'strErrorStr'), 'errno' : 1, + {'args' : (1, 'strErrorStr', 'filenameStr'), 'errno' : 1, 'strerror' : 'strErrorStr', 'filename' : 'filenameStr', 'filename2' : None}), + (OSError, (), {'filename': 'filenameStr'}, + {'args' : (None, None, 'filenameStr'), + 'errno' : None, 'strerror' : None, + 'filename' : 'filenameStr', 'filename2' : None}), + (OSError, ('strErrorStr',), {'filename': 'filenameStr'}, + {'args' : (None, 'strErrorStr', 'filenameStr'), + 'errno' : None, 'strerror' : 'strErrorStr', + 'filename' : 'filenameStr', 'filename2' : None}), + (FileNotFoundError, (), {}, + {'args' : (errno.ENOENT, os.strerror(errno.ENOENT)), + 'errno' : errno.ENOENT, + 'strerror' : os.strerror(errno.ENOENT), + 'filename' : None, 'filename2' : None}), + (FileNotFoundError, (), {'filename': 'filenameStr'}, + {'args' : (errno.ENOENT, os.strerror(errno.ENOENT), + 'filenameStr'), + 'errno' : errno.ENOENT, + 'strerror' : os.strerror(errno.ENOENT), + 'filename' : 'filenameStr', 'filename2' : None}), + (BlockingIOError, (), {'characters_written': 5}, + {'args' : (errno.EAGAIN, os.strerror(errno.EAGAIN), 5), + 'errno' : errno.EAGAIN, + 'strerror' : os.strerror(errno.EAGAIN), + 'filename' : None, 'filename2' : None, + 'characters_written' : 5}), + (BlockingIOError, (errno.EAGAIN, 'strErrorStr', 3), {}, + {'args' : (errno.EAGAIN, 'strErrorStr', 3), + 'errno' : errno.EAGAIN, 'strerror' : 'strErrorStr', + 'filename' : None, 'filename2' : None, + 'characters_written' : 3}), (SyntaxError, (), {}, {'msg' : None, 'text' : None, 'filename' : None, 'lineno' : None, 'offset' : None, 'end_offset': None, 'print_file_and_line' : None}), @@ -546,11 +584,36 @@ def testAttributes(self): # More tests are in test_WindowsError exceptionList.append( (WindowsError, (1, 'strErrorStr', 'filenameStr'), {}, - {'args' : (1, 'strErrorStr'), + {'args' : (1, 'strErrorStr', 'filenameStr'), 'strerror' : 'strErrorStr', 'winerror' : None, 'errno' : 1, 'filename' : 'filenameStr', 'filename2' : None}) ) + # ERROR_PATH_NOT_FOUND (3) is translated to ENOENT + exceptionList.append( + (OSError, (0, 'strErrorStr', 'filenameStr', 3), {}, + {'args' : (errno.ENOENT, 'strErrorStr', 'filenameStr', 3), + 'strerror' : 'strErrorStr', 'winerror' : 3, + 'errno' : errno.ENOENT, + 'filename' : 'filenameStr', 'filename2' : None}) + ) + exceptionList.append( + (OSError, (0, 'strErrorStr', 'filenameStr', 3, 'filename2Str'), + {}, + {'args' : (errno.ENOENT, 'strErrorStr', 'filenameStr', 3, + 'filename2Str'), + 'strerror' : 'strErrorStr', 'winerror' : 3, + 'errno' : errno.ENOENT, + 'filename' : 'filenameStr', 'filename2' : 'filename2Str'}) + ) + exceptionList.append( + (OSError, ('strErrorStr',), + {'winerror': 3, 'filename': 'filenameStr'}, + {'args' : (errno.ENOENT, 'strErrorStr', 'filenameStr', 3), + 'strerror' : 'strErrorStr', 'winerror' : 3, + 'errno' : errno.ENOENT, + 'filename' : 'filenameStr', 'filename2' : None}) + ) except NameError: pass diff --git a/Lib/test/test_xpickle.py b/Lib/test/test_xpickle.py index d87c671d4f5394..8a165a81abcc20 100644 --- a/Lib/test/test_xpickle.py +++ b/Lib/test/test_xpickle.py @@ -1,5 +1,6 @@ # This test covers backwards compatibility with previous versions of Python # by bouncing pickled objects through Python versions by running xpickle_worker.py. +import errno import io import os import pickle @@ -224,6 +225,33 @@ def test_bytes(self): test_recursive_nested_names = None test_recursive_nested_names2 = None + def test_oserror_attributes(self): + # An OSError is reconstructed by calling its class with args, so its + # attributes have to survive being built by the other version. + if self.py_version < (3, 3): + self.skipTest('OSError subclasses need Python >= 3.3') + strerror = os.strerror(errno.ENOENT) + cases = [ + OSError(errno.ENOENT, strerror), + OSError(errno.ENOENT, strerror, 'foo.txt'), + OSError(errno.ENOENT, strerror, 'foo.txt', None, 'bar.txt'), + FileNotFoundError(filename='foo.txt'), + BlockingIOError(errno.EAGAIN, 'would block', 5), + BlockingIOError(characters_written=5), + ] + for orig in cases: + for proto in pickletester.protocols: + with self.subTest(exc=repr(orig), proto=proto): + exc = self.loads(self.dumps(orig, proto)) + self.assertIs(type(exc), type(orig)) + self.assertEqual(exc.args, orig.args) + self.assertEqual(exc.errno, orig.errno) + self.assertEqual(exc.strerror, orig.strerror) + self.assertEqual(exc.filename, orig.filename) + self.assertEqual(exc.filename2, orig.filename2) + self.assertEqual(getattr(exc, 'characters_written', None), + getattr(orig, 'characters_written', None)) + # Attribute lookup problems are expected, disable the test test_dynamic_class = None test_evil_class_mutating_dict = None diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-11-42-18.gh-issue-109714.Kq3Rvb.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-11-42-18.gh-issue-109714.Kq3Rvb.rst new file mode 100644 index 00000000000000..a701301bd71258 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-11-42-18.gh-issue-109714.Kq3Rvb.rst @@ -0,0 +1,13 @@ +Improve the :exc:`OSError` constructor. +The *errno* argument can now be omitted: +it then defaults to the error code which corresponds to the exception class, +like :data:`~errno.ENOENT` for :exc:`FileNotFoundError`. +The *strerror* argument can be omitted too; +it is derived from the resulting :attr:`~OSError.errno`, +or on Windows from :attr:`~OSError.winerror` if that was given. +The *filename*, *winerror* and *filename2* arguments +can now be passed by keyword, +as can *characters_written* for :exc:`BlockingIOError`. +The :attr:`~BaseException.args` attribute is no longer truncated +when a file name is given, +so that an exception which carries a file name now survives pickling. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index cc3e03baa4c7df..583018218a0f24 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2048,131 +2048,207 @@ PyOSErrorObject_CAST(PyObject *self) #include "errmap.h" #endif -/* Where a function has a single filename, such as open() or some - * of the os module functions, PyErr_SetFromErrnoWithFilename() is - * called, giving a third argument which is the filename. But, so - * that old code using in-place unpacking doesn't break, e.g.: - * - * except OSError, (errno, strerror): - * - * we hack args so that it only contains two items. This also - * means we need our own __str__() which prints out the filename - * when it was supplied. - * - * (If a function has two filenames, such as rename(), symlink(), - * or copy(), PyErr_SetFromErrnoWithFilenameObjects() is called, - * which allows passing in a second filename.) - */ +/* Return the canonical positional form (errno, strerror, filename, winerror, + filename2), so that OSError(*args) reproduces the exception. Omitted values + are None, and trailing ones are left out. */ +static PyObject * +oserror_canonical_args(PyObject *args, PyObject *myerrno, PyObject *strerror, + PyObject *filename, PyObject *winerror, + PyObject *filename2) +{ + PyObject *items[5] = {myerrno, strerror, filename, winerror, filename2}; + Py_ssize_t newsize = 0; + for (Py_ssize_t i = 0; i < 5; i++) { + if (items[i] == NULL) { + items[i] = Py_None; + } + else { + newsize = i + 1; + } + } + if (newsize <= 2) { + if (myerrno == NULL) { + return Py_NewRef(args); + } + /* The canonical form always starts with errno and strerror. */ + newsize = 2; + } + return PyTuple_FromArray(items, newsize); +} -/* This function doesn't cleanup on error, the caller should */ -static int -oserror_parse_args(PyObject **p_args, - PyObject **myerrno, PyObject **strerror, - PyObject **filename, PyObject **filename2 +/* Return the errno which OSError() would end up with, so that the class can + be chosen before the arguments are parsed in full. */ +static PyObject * +oserror_errno_arg(PyObject *args, PyObject *kwargs) +{ + Py_ssize_t nargs = PyTuple_GET_SIZE(args); #ifdef MS_WINDOWS - , PyObject **winerror -#endif - ) -{ - Py_ssize_t nargs; - PyObject *args = *p_args; -#ifndef MS_WINDOWS - /* - * ignored on non-Windows platforms, - * but parsed so OSError has a consistent signature - */ - PyObject *_winerror = NULL; - PyObject **winerror = &_winerror; + PyObject *winerror = NULL; + if (nargs >= 4) { + winerror = Py_NewRef(PyTuple_GET_ITEM(args, 3)); + } + else if (kwargs != NULL && + PyDict_GetItemStringRef(kwargs, "winerror", &winerror) < 0) + { + return NULL; + } + if (winerror != NULL) { + if (PyLong_Check(winerror)) { + long winerrcode = PyLong_AsLong(winerror); + Py_DECREF(winerror); + if (winerrcode == -1 && PyErr_Occurred()) { + return NULL; + } + return PyLong_FromLong(winerror_to_errno(winerrcode)); + } + Py_DECREF(winerror); + } #endif /* MS_WINDOWS */ + if (nargs >= 2) { + return Py_NewRef(PyTuple_GET_ITEM(args, 0)); + } + Py_RETURN_NONE; +} - nargs = PyTuple_GET_SIZE(args); +/* This function doesn't cleanup on error, the caller should */ +static int +oserror_init(PyOSErrorObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *myerrno = NULL, *mystrerror = NULL; + PyObject *filename = NULL, *filename2 = NULL; + PyObject *written = NULL, *winerror = NULL; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); - if (nargs >= 2 && nargs <= 5) { - if (!PyArg_UnpackTuple(args, "OSError", 2, 5, - myerrno, strerror, - filename, winerror, filename2)) - return -1; + /* characters_written is accepted for every class, and rejected below + for all but BlockingIOError, which is only known once errno is. */ + static char *keywords[] = { + "", "", "filename", "winerror", "filename2", "characters_written", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOO$O:OSError", keywords, + &myerrno, &mystrerror, &filename, &winerror, &filename2, + &written)) + { + return -1; + } + if (nargs < 2) { + /* a lone positional argument is strerror */ + mystrerror = myerrno; + myerrno = NULL; + } #ifdef MS_WINDOWS - if (*winerror && PyLong_Check(*winerror)) { - long errcode, winerrcode; - PyObject *newargs; - Py_ssize_t i; + long winerrcode = 0; + int have_winerror = winerror != NULL && PyLong_Check(winerror); + if (have_winerror) { + winerrcode = PyLong_AsLong(winerror); + if (winerrcode == -1 && PyErr_Occurred()) { + return -1; + } + myerrno = PyLong_FromLong(winerror_to_errno(winerrcode)); + if (myerrno == NULL) { + return -1; + } + } + else +#endif /* MS_WINDOWS */ + if (myerrno != NULL) { + Py_INCREF(myerrno); + } + else { + /* errno is taken from the class when it was not given, and stays + NULL if the class has no default_errno. */ + if (PyObject_GetOptionalAttrString((PyObject *)Py_TYPE(self), + "default_errno", &myerrno) < 0) + { + return -1; + } + } + Py_XSETREF(self->myerrno, myerrno); - winerrcode = PyLong_AsLong(*winerror); - if (winerrcode == -1 && PyErr_Occurred()) - return -1; - errcode = winerror_to_errno(winerrcode); - *myerrno = PyLong_FromLong(errcode); - if (!*myerrno) - return -1; - newargs = PyTuple_New(nargs); - if (!newargs) + /* strerror is derived from the error code when it was not given. */ + if (mystrerror != NULL) { + Py_INCREF(mystrerror); + } + else { +#ifdef MS_WINDOWS + /* more specific than the message for the translated errno */ + if (have_winerror) { + mystrerror = _PyErr_WindowsErrorMessage((unsigned long)winerrcode); + if (mystrerror == NULL) { return -1; - PyTuple_SET_ITEM(newargs, 0, *myerrno); - for (i = 1; i < nargs; i++) { - PyObject *val = PyTuple_GET_ITEM(args, i); - PyTuple_SET_ITEM(newargs, i, Py_NewRef(val)); } - Py_DECREF(args); - args = *p_args = newargs; } #endif /* MS_WINDOWS */ + if (mystrerror == NULL && myerrno != NULL && PyLong_Check(myerrno)) { + int overflow; + long err = PyLong_AsLongAndOverflow(myerrno, &overflow); + if (err == -1 && PyErr_Occurred()) { + return -1; + } + /* not an errno value; leave strerror unset */ + if (!overflow && err <= INT_MAX && err >= INT_MIN) { + const char *s = strerror((int)err); + mystrerror = s != NULL + ? PyUnicode_DecodeLocale(s, "surrogateescape") + : PyUnicode_FromString("Error"); + if (mystrerror == NULL) { + return -1; + } + } + } } - - return 0; -} - -static int -oserror_init(PyOSErrorObject *self, PyObject **p_args, - PyObject *myerrno, PyObject *strerror, - PyObject *filename, PyObject *filename2 + Py_XSETREF(self->strerror, mystrerror); #ifdef MS_WINDOWS - , PyObject *winerror + Py_XSETREF(self->winerror, Py_XNewRef(winerror)); #endif - ) -{ - PyObject *args = *p_args; - Py_ssize_t nargs = PyTuple_GET_SIZE(args); - /* self->filename will remain Py_None otherwise */ - if (filename && filename != Py_None) { - if (Py_IS_TYPE(self, (PyTypeObject *) PyExc_BlockingIOError) && - PyNumber_Check(filename)) { - /* BlockingIOError's 3rd argument can be the number of - * characters written. - */ - self->written = PyNumber_AsSsize_t(filename, PyExc_ValueError); - if (self->written == -1 && PyErr_Occurred()) - return -1; + /* filename and characters_written are alternative spellings of the + third argument of BlockingIOError */ + if (!Py_IS_TYPE(self, (PyTypeObject *) PyExc_BlockingIOError)) { + if (written != NULL) { + PyErr_Format(PyExc_TypeError, + "%s() got an unexpected keyword argument " + "'characters_written'", Py_TYPE(self)->tp_name); + return -1; } - else { + } + else if (filename == NULL) { + filename = written; + } + else if (written != NULL) { + PyErr_SetString(PyExc_TypeError, + "BlockingIOError() takes either filename or " + "characters_written, not both"); + return -1; + } + else if (PyNumber_Check(filename)) { + written = filename; + } + + if (written) { + Py_ssize_t n = PyNumber_AsSsize_t(written, PyExc_ValueError); + if (n == -1 && PyErr_Occurred()) { + return -1; + } + self->written = n; + } + else { + /* self->filename will remain Py_None otherwise */ + if (filename && filename != Py_None) { Py_XSETREF(self->filename, Py_NewRef(filename)); if (filename2 && filename2 != Py_None) { Py_XSETREF(self->filename2, Py_NewRef(filename2)); } - - if (nargs >= 2 && nargs <= 5) { - /* filename, filename2, and winerror are removed from the args tuple - (for compatibility purposes, see test_exceptions.py) */ - PyObject *subslice = PyTuple_GetSlice(args, 0, 2); - if (!subslice) - return -1; - - Py_DECREF(args); /* replacing args */ - *p_args = args = subslice; - } } } - Py_XSETREF(self->myerrno, Py_XNewRef(myerrno)); - Py_XSETREF(self->strerror, Py_XNewRef(strerror)); -#ifdef MS_WINDOWS - Py_XSETREF(self->winerror, Py_XNewRef(winerror)); -#endif - /* Steals the reference to args */ - Py_XSETREF(self->args, args); - *p_args = args = NULL; + PyObject *newargs = oserror_canonical_args(args, self->myerrno, + self->strerror, filename, + winerror, filename2); + if (newargs == NULL) { + return -1; + } + /* Steals the reference to newargs */ + Py_XSETREF(self->args, newargs); return 0; } @@ -2182,6 +2258,7 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds); static int OSError_init(PyObject *self, PyObject *args, PyObject *kwds); + static int oserror_use_init(PyTypeObject *type) { @@ -2206,37 +2283,25 @@ static PyObject * OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyOSErrorObject *self = NULL; - PyObject *myerrno = NULL, *strerror = NULL; - PyObject *filename = NULL, *filename2 = NULL; -#ifdef MS_WINDOWS - PyObject *winerror = NULL; -#endif - - Py_INCREF(args); - - if (!oserror_use_init(type)) { - if (!_PyArg_NoKeywords(type->tp_name, kwds)) - goto error; - if (oserror_parse_args(&args, &myerrno, &strerror, - &filename, &filename2 -#ifdef MS_WINDOWS - , &winerror -#endif - )) + if ((PyObject *) type == PyExc_OSError) { + PyObject *errcode = oserror_errno_arg(args, kwds); + if (errcode == NULL) goto error; struct _Py_exc_state *state = get_exc_state(); - if (myerrno && PyLong_Check(myerrno) && - state->errnomap && (PyObject *) type == PyExc_OSError) { + if (PyLong_Check(errcode) && state->errnomap) { PyObject *newtype; - newtype = PyDict_GetItemWithError(state->errnomap, myerrno); + newtype = PyDict_GetItemWithError(state->errnomap, errcode); if (newtype) { type = _PyType_CAST(newtype); } - else if (PyErr_Occurred()) + else if (PyErr_Occurred()) { + Py_DECREF(errcode); goto error; + } } + Py_DECREF(errcode); } self = (PyOSErrorObject *) type->tp_alloc(type, 0); @@ -2248,11 +2313,7 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->written = -1; if (!oserror_use_init(type)) { - if (oserror_init(self, &args, myerrno, strerror, filename, filename2 -#ifdef MS_WINDOWS - , winerror -#endif - )) + if (oserror_init(self, args, kwds)) goto error; } else { @@ -2261,11 +2322,9 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) goto error; } - Py_XDECREF(args); return (PyObject *) self; error: - Py_XDECREF(args); Py_XDECREF(self); return NULL; } @@ -2274,39 +2333,12 @@ static int OSError_init(PyObject *op, PyObject *args, PyObject *kwds) { PyOSErrorObject *self = PyOSErrorObject_CAST(op); - PyObject *myerrno = NULL, *strerror = NULL; - PyObject *filename = NULL, *filename2 = NULL; -#ifdef MS_WINDOWS - PyObject *winerror = NULL; -#endif if (!oserror_use_init(Py_TYPE(self))) /* Everything already done in OSError_new */ return 0; - if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds)) - return -1; - - Py_INCREF(args); - if (oserror_parse_args(&args, &myerrno, &strerror, &filename, &filename2 -#ifdef MS_WINDOWS - , &winerror -#endif - )) - goto error; - - if (oserror_init(self, &args, myerrno, strerror, filename, filename2 -#ifdef MS_WINDOWS - , winerror -#endif - )) - goto error; - - return 0; - -error: - Py_DECREF(args); - return -1; + return oserror_init(self, args, kwds); } static int @@ -2350,79 +2382,100 @@ OSError_str(PyObject *op) { PyOSErrorObject *self = PyOSErrorObject_CAST(op); #define OR_NONE(x) ((x)?(x):Py_None) +/* An omitted value and an explicit None are both "not given": neither is + worth rendering as "[Errno None]". */ +#define PRESENT(x) ((x) != NULL && (x) != Py_None) #ifdef MS_WINDOWS /* If available, winerror has the priority over myerrno */ - if (self->winerror && self->filename) { + if (PRESENT(self->winerror) && self->filename) { if (self->filename2) { return PyUnicode_FromFormat("[WinError %S] %S: %R -> %R", - OR_NONE(self->winerror), + self->winerror, OR_NONE(self->strerror), self->filename, self->filename2); - } else { - return PyUnicode_FromFormat("[WinError %S] %S: %R", - OR_NONE(self->winerror), - OR_NONE(self->strerror), - self->filename); } + return PyUnicode_FromFormat("[WinError %S] %S: %R", + self->winerror, + OR_NONE(self->strerror), + self->filename); } - if (self->winerror && self->strerror) + if (PRESENT(self->winerror) && PRESENT(self->strerror)) return PyUnicode_FromFormat("[WinError %S] %S", - self->winerror ? self->winerror: Py_None, - self->strerror ? self->strerror: Py_None); + self->winerror, + self->strerror); #endif - if (self->filename) { - if (self->filename2) { - return PyUnicode_FromFormat("[Errno %S] %S: %R -> %R", - OR_NONE(self->myerrno), - OR_NONE(self->strerror), - self->filename, - self->filename2); - } else { + if (PRESENT(self->myerrno)) { + if (self->filename) { + if (self->filename2) { + return PyUnicode_FromFormat("[Errno %S] %S: %R -> %R", + self->myerrno, + OR_NONE(self->strerror), + self->filename, + self->filename2); + } return PyUnicode_FromFormat("[Errno %S] %S: %R", - OR_NONE(self->myerrno), + self->myerrno, OR_NONE(self->strerror), self->filename); } + if (PRESENT(self->strerror)) + return PyUnicode_FromFormat("[Errno %S] %S", + self->myerrno, self->strerror); + } + else if (self->filename) { + /* filename can now be given by keyword alone, without an errno. */ + if (PRESENT(self->strerror)) { + if (self->filename2) { + return PyUnicode_FromFormat("%S: %R -> %R", + self->strerror, + self->filename, + self->filename2); + } + return PyUnicode_FromFormat("%S: %R", + self->strerror, self->filename); + } + if (self->filename2) { + return PyUnicode_FromFormat("%R -> %R", + self->filename, self->filename2); + } + return PyUnicode_FromFormat("%R", self->filename); } - if (self->myerrno && self->strerror) - return PyUnicode_FromFormat("[Errno %S] %S", - self->myerrno, self->strerror); return BaseException_str(op); +#undef PRESENT +#undef OR_NONE } static PyObject * OSError_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) { PyOSErrorObject *self = PyOSErrorObject_CAST(op); - PyObject *args = self->args; PyObject *res = NULL; - /* self->args is only the first two real arguments if there was a - * file name given to OSError. */ - if (PyTuple_GET_SIZE(args) == 2 && self->filename) { - Py_ssize_t size = self->filename2 ? 5 : 3; - args = PyTuple_New(size); - if (!args) + /* The attributes can also be set after the exception was constructed, as + * shutil and pathlib do with the file names, so build args from them + * rather than reusing the tuple the constructor left. */ + PyObject *written = NULL; + if (self->written != -1) { + written = PyLong_FromSsize_t(self->written); + if (written == NULL) { return NULL; - - PyTuple_SET_ITEM(args, 0, Py_NewRef(PyTuple_GET_ITEM(self->args, 0))); - PyTuple_SET_ITEM(args, 1, Py_NewRef(PyTuple_GET_ITEM(self->args, 1))); - PyTuple_SET_ITEM(args, 2, Py_NewRef(self->filename)); - - if (self->filename2) { - /* - * This tuple is essentially used as OSError(*args). - * So, to recreate filename2, we need to pass in - * winerror as well. - */ - PyTuple_SET_ITEM(args, 3, Py_NewRef(Py_None)); - - /* filename2 */ - PyTuple_SET_ITEM(args, 4, Py_NewRef(self->filename2)); } - } else - Py_INCREF(args); + } +#ifdef MS_WINDOWS + PyObject *winerror = self->winerror; +#else + PyObject *winerror = NULL; +#endif + PyObject *args = oserror_canonical_args(self->args, self->myerrno, + self->strerror, + self->filename ? self->filename + : written, + winerror, self->filename2); + Py_XDECREF(written); + if (args == NULL) { + return NULL; + } if (self->dict) res = PyTuple_Pack(3, Py_TYPE(self), args, self->dict); @@ -4594,7 +4647,7 @@ _PyExc_InitState(PyInterpreterState *interp) { struct _Py_exc_state *state = &interp->exc_state; -#define ADD_ERRNO(TYPE, CODE) \ +#define ADD_ERRNO_X(TYPE, CODE, DEFAULT) \ do { \ PyObject *_code = PyLong_FromLong(CODE); \ assert(_PyObject_RealIsSubclass(PyExc_ ## TYPE, PyExc_OSError)); \ @@ -4602,9 +4655,16 @@ _PyExc_InitState(PyInterpreterState *interp) Py_XDECREF(_code); \ return _PyStatus_ERR("errmap insertion problem."); \ } \ + if (DEFAULT && PyDict_SetItemString(_PyType_GetDict(_PyType_CAST(PyExc_ ## TYPE)), "default_errno", _code) < 0) { \ + Py_DECREF(_code); \ + return _PyStatus_ERR("default errno setting problem."); \ + } \ Py_DECREF(_code); \ } while (0) +#define ADD_ERRNO(TYPE, CODE) ADD_ERRNO_X(TYPE, CODE, 1) +#define ADD_ALT_ERRNO(TYPE, CODE) ADD_ERRNO_X(TYPE, CODE, 0) + /* Add exceptions to errnomap */ assert(state->errnomap == NULL); state->errnomap = PyDict_New(); @@ -4613,12 +4673,12 @@ _PyExc_InitState(PyInterpreterState *interp) } ADD_ERRNO(BlockingIOError, EAGAIN); - ADD_ERRNO(BlockingIOError, EALREADY); - ADD_ERRNO(BlockingIOError, EINPROGRESS); - ADD_ERRNO(BlockingIOError, EWOULDBLOCK); + ADD_ALT_ERRNO(BlockingIOError, EALREADY); + ADD_ALT_ERRNO(BlockingIOError, EINPROGRESS); + ADD_ALT_ERRNO(BlockingIOError, EWOULDBLOCK); ADD_ERRNO(BrokenPipeError, EPIPE); #ifdef ESHUTDOWN - ADD_ERRNO(BrokenPipeError, ESHUTDOWN); + ADD_ALT_ERRNO(BrokenPipeError, ESHUTDOWN); #endif ADD_ERRNO(ChildProcessError, ECHILD); ADD_ERRNO(ConnectionAbortedError, ECONNABORTED); @@ -4630,11 +4690,11 @@ _PyExc_InitState(PyInterpreterState *interp) ADD_ERRNO(NotADirectoryError, ENOTDIR); ADD_ERRNO(InterruptedError, EINTR); ADD_ERRNO(PermissionError, EACCES); - ADD_ERRNO(PermissionError, EPERM); + ADD_ALT_ERRNO(PermissionError, EPERM); #ifdef ENOTCAPABLE // Extension for WASI capability-based security. Process lacks // capability to access a resource. - ADD_ERRNO(PermissionError, ENOTCAPABLE); + ADD_ALT_ERRNO(PermissionError, ENOTCAPABLE); #endif ADD_ERRNO(ProcessLookupError, ESRCH); ADD_ERRNO(TimeoutError, ETIMEDOUT); @@ -4645,6 +4705,8 @@ _PyExc_InitState(PyInterpreterState *interp) return _PyStatus_OK(); #undef ADD_ERRNO +#undef ADD_ALT_ERRNO +#undef ADD_ERRNO_X } diff --git a/Python/errors.c b/Python/errors.c index 48b03e5fd714b1..6350d3ebc211f5 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -818,9 +818,6 @@ PyErr_SetFromErrnoWithFilenameObjects(PyObject *exc, PyObject *filenameObject, P PyObject *message; PyObject *v, *args; int i = errno; -#ifdef MS_WINDOWS - WCHAR *s_buf = NULL; -#endif /* Unix/Windows */ #ifdef EINTR if (i == EINTR && PyErr_CheckSignals()) @@ -850,38 +847,12 @@ PyErr_SetFromErrnoWithFilenameObjects(PyObject *exc, PyObject *filenameObject, P message = PyUnicode_FromString(_sys_errlist[i]); } else { - int len = FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, /* no message source */ - i, - MAKELANGID(LANG_NEUTRAL, - SUBLANG_DEFAULT), - /* Default language */ - (LPWSTR) &s_buf, - 0, /* size not used */ - NULL); /* no args */ - if (len==0) { - /* Only ever seen this in out-of-mem - situations */ - s_buf = NULL; - message = PyUnicode_FromFormat("Windows Error 0x%x", i); - } else { - /* remove trailing cr/lf and dots */ - while (len > 0 && (s_buf[len-1] <= L' ' || s_buf[len-1] == L'.')) - s_buf[--len] = L'\0'; - message = PyUnicode_FromWideChar(s_buf, len); - } + message = _PyErr_WindowsErrorMessage(i); } } #endif /* Unix/Windows */ - if (message == NULL) - { -#ifdef MS_WINDOWS - LocalFree(s_buf); -#endif + if (message == NULL) { return NULL; } @@ -904,9 +875,6 @@ PyErr_SetFromErrnoWithFilenameObjects(PyObject *exc, PyObject *filenameObject, P Py_DECREF(v); } } -#ifdef MS_WINDOWS - LocalFree(s_buf); -#endif return NULL; } @@ -935,6 +903,36 @@ PyErr_SetFromErrno(PyObject *exc) #ifdef MS_WINDOWS /* Windows specific error code handling */ + +PyObject * +_PyErr_WindowsErrorMessage(unsigned long err) +{ + WCHAR *s_buf = NULL; /* Free via LocalFree */ + int len = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, /* no message source */ + err, + MAKELANGID(LANG_NEUTRAL, + SUBLANG_DEFAULT), + /* Default language */ + (LPWSTR) &s_buf, + 0, /* size not used */ + NULL); /* no args */ + if (len == 0) { + /* Only ever seen this in out-of-mem situations */ + return PyUnicode_FromFormat("Windows Error 0x%x", err); + } + /* remove trailing cr/lf and dots */ + while (len > 0 && (s_buf[len-1] <= L' ' || s_buf[len-1] == L'.')) { + s_buf[--len] = L'\0'; + } + PyObject *message = PyUnicode_FromWideChar(s_buf, len); + LocalFree(s_buf); + return message; +} + PyObject *PyErr_SetExcFromWindowsErrWithFilenameObject( PyObject *exc, int ierr, @@ -951,9 +949,6 @@ PyObject *PyErr_SetExcFromWindowsErrWithFilenameObjects( PyObject *filenameObject2) { PyThreadState *tstate = _PyThreadState_GET(); - int len; - WCHAR *s_buf = NULL; /* Free via LocalFree */ - PyObject *message; PyObject *args, *v; DWORD err = (DWORD)ierr; @@ -961,32 +956,8 @@ PyObject *PyErr_SetExcFromWindowsErrWithFilenameObjects( err = GetLastError(); } - len = FormatMessageW( - /* Error API error */ - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, /* no message source */ - err, - MAKELANGID(LANG_NEUTRAL, - SUBLANG_DEFAULT), /* Default language */ - (LPWSTR) &s_buf, - 0, /* size not used */ - NULL); /* no args */ - if (len==0) { - /* Only seen this in out of mem situations */ - message = PyUnicode_FromFormat("Windows Error 0x%x", err); - s_buf = NULL; - } else { - /* remove trailing cr/lf and dots */ - while (len > 0 && (s_buf[len-1] <= L' ' || s_buf[len-1] == L'.')) - s_buf[--len] = L'\0'; - message = PyUnicode_FromWideChar(s_buf, len); - } - - if (message == NULL) - { - LocalFree(s_buf); + PyObject *message = _PyErr_WindowsErrorMessage(err); + if (message == NULL) { return NULL; } @@ -1009,7 +980,6 @@ PyObject *PyErr_SetExcFromWindowsErrWithFilenameObjects( Py_DECREF(v); } } - LocalFree(s_buf); return NULL; }