diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b541dbd0c23d326..98df879413c72ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -554,7 +554,6 @@ jobs: - Thread free-threading: - false - - true sanitizer: - TSan include: @@ -566,6 +565,17 @@ jobs: sanitizer: ${{ matrix.sanitizer }} free-threading: ${{ matrix.free-threading }} + # XXX: Temporarily allow this job to fail to not block PRs. + build-san-free-threading: + # ${{ '' } is a hack to nest jobs under the same sidebar category. + name: Sanitizers${{ '' }} # zizmor: ignore[obfuscation] + needs: build-context + if: needs.build-context.outputs.run-ubuntu == 'true' + uses: ./.github/workflows/reusable-san.yml + with: + sanitizer: TSan + free-threading: true + cross-build-linux: name: Cross build Linux runs-on: ubuntu-26.04 @@ -669,6 +679,7 @@ jobs: - test-hypothesis - build-asan - build-san + - build-san-free-threading - cross-build-linux - cifuzz if: always() @@ -680,6 +691,7 @@ jobs: allowed-failures: >- build-android, build-emscripten, + build-san-free-threading, build-windows-msi, build-ubuntu-ssltests, test-hypothesis, diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index dbd73313b4f7779..c2546b7fc451aed 100644 --- a/Doc/deprecations/pending-removal-in-3.21.rst +++ b/Doc/deprecations/pending-removal-in-3.21.rst @@ -23,3 +23,8 @@ Pending removal in Python 3.21 * Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes are now deprecated. These codes will be removed in Python 3.21. Use instead two-letter forms ``'Zf'`` and ``'Zd'``. + +* :mod:`tempfile`: + + * ``tempfile._TemporaryFileWrapper`` will be removed in Python 3.21. Use the + public :class:`tempfile.TemporaryFileWrapper` instead. diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index d0fe0625deb5133..9a41e406c8c10e6 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -2240,8 +2240,9 @@ always available. Unless explicitly noted otherwise, all variables are read-only The name of the lock implementation: - * ``"semaphore"``: a lock uses a semaphore - * ``"mutex+cond"``: a lock uses a mutex and a condition variable + * ``"semaphore"``: a lock uses a semaphore (Python 3.14 and older) + * ``"mutex+cond"``: a lock uses a mutex and a condition variable (Python 3.14 and older) + * ``"pymutex"``: a lock uses the :c:type:`PyMutex` implementation (Python 3.15 and newer) * ``None`` if this information is unknown .. attribute:: thread_info.version diff --git a/Doc/library/tempfile.rst b/Doc/library/tempfile.rst index bf9198e175a0e11..8c387853d0417f4 100644 --- a/Doc/library/tempfile.rst +++ b/Doc/library/tempfile.rst @@ -14,10 +14,11 @@ This module creates temporary files and directories. It works on all supported platforms. :class:`TemporaryFile`, :class:`NamedTemporaryFile`, -:class:`TemporaryDirectory`, and :class:`SpooledTemporaryFile` are high-level -interfaces which provide automatic cleanup and can be used as -:term:`context managers `. :func:`mkstemp` and -:func:`mkdtemp` are lower-level functions which require manual cleanup. +:class:`TemporaryFileWrapper`, :class:`TemporaryDirectory`, and +:class:`SpooledTemporaryFile` are high-level interfaces which provide +automatic cleanup and can be used as :term:`context managers +`. :func:`mkstemp` and :func:`mkdtemp` are lower-level +functions which require manual cleanup. All the user-callable functions and constructors take additional arguments which allow direct control over the location and name of temporary files and @@ -84,13 +85,13 @@ The module defines the following user-callable items: :func:`TemporaryFile` with *delete* and *delete_on_close* parameters that determine whether and how the named file should be automatically deleted. - The returned object is always a :term:`file-like object` whose :attr:`!file` - attribute is the underlying true file object. This file-like object - can be used in a :keyword:`with` statement, just like a normal file. The - name of the temporary file can be retrieved from the :attr:`!name` attribute - of the returned file-like object. On Unix, unlike with the - :func:`TemporaryFile`, the directory entry does not get unlinked immediately - after the file creation. + The returned object is always a :class:`TemporaryFileWrapper` instance + (a :term:`file-like object`) whose :attr:`~TemporaryFileWrapper.file` attribute is the underlying + true file object. This file-like object can be used in a :keyword:`with` + statement, just like a normal file. The name of the temporary file can be + retrieved from the :attr:`!name` attribute of the returned file-like object. + On Unix, unlike with the :func:`TemporaryFile`, the directory entry does not + get unlinked immediately after the file creation. If *delete* is true (the default) and *delete_on_close* is true (the default), the file is deleted as soon as it is closed. If *delete* is true @@ -140,6 +141,34 @@ The module defines the following user-callable items: .. versionchanged:: 3.12 Added *delete_on_close* parameter. +.. class:: TemporaryFileWrapper(file, name, delete=True, delete_on_close=True) + + A mutable wrapper returned by :func:`NamedTemporaryFile`. It wraps the + underlying file object, delegating attribute access to it, and ensures + the temporary file is deleted when appropriate. + + .. attribute:: file + + The underlying :term:`file-like object`. + + .. attribute:: name + + The file name of the temporary file. + + .. method:: close() + + Close the temporary file, possibly deleting it depending on the + *delete* and *delete_on_close* arguments passed to + :func:`NamedTemporaryFile`. + + .. note:: + + ``tempfile._TemporaryFileWrapper`` is kept as a backwards compatible + deprecated alias for this class. + It will be removed in Python 3.21 + + .. versionadded:: next + .. class:: SpooledTemporaryFile(max_size=0, mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index dd2c7b68d039921..43ecdb41afc3a9c 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -588,6 +588,13 @@ New deprecations two-letter forms ``'Zf'`` and ``'Zd'``. (Contributed by Sergey B Kirpichev in :gh:`121249`.) +* :mod:`tempfile` + + * The private ``tempfile._TemporaryFileWrapper`` name is deprecated + and is slated for removal in Python 3.21. + Use the new public :class:`tempfile.TemporaryFileWrapper` instead, + which is the return type of :func:`tempfile.NamedTemporaryFile`. + .. Add deprecations above alphabetically, not here at the end. .. include:: ../deprecations/pending-removal-in-3.17.rst diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 93bd7df993d8270..3836aaad326f62e 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -1219,21 +1219,26 @@ async def _create_connection_transport( ssl_handshake_timeout=None, ssl_shutdown_timeout=None, context=None): - sock.setblocking(False) - context = context if context is not None else contextvars.copy_context() - - protocol = protocol_factory() - waiter = self.create_future() - if ssl: - sslcontext = None if isinstance(ssl, bool) else ssl - transport = self._make_ssl_transport( - sock, protocol, sslcontext, waiter, - server_side=server_side, server_hostname=server_hostname, - ssl_handshake_timeout=ssl_handshake_timeout, - ssl_shutdown_timeout=ssl_shutdown_timeout, - context=context) - else: - transport = self._make_socket_transport(sock, protocol, waiter, context=context) + try: + sock.setblocking(False) + context = context if context is not None else contextvars.copy_context() + + protocol = protocol_factory() + waiter = self.create_future() + if ssl: + sslcontext = None if isinstance(ssl, bool) else ssl + transport = self._make_ssl_transport( + sock, protocol, sslcontext, waiter, + server_side=server_side, server_hostname=server_hostname, + ssl_handshake_timeout=ssl_handshake_timeout, + ssl_shutdown_timeout=ssl_shutdown_timeout, + context=context) + else: + transport = self._make_socket_transport(sock, protocol, waiter, context=context) + except: + # gh-153133: close the socket if the transport is never created. + sock.close() + raise try: await waiter diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 6dac9ab3c417171..485038417c65c66 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -31,6 +31,7 @@ "TMP_MAX", "gettempprefix", # constants "tempdir", "gettempdir", "gettempprefixb", "gettempdirb", + "TemporaryFileWrapper", ] @@ -484,7 +485,7 @@ def __del__(self): _warnings.warn(self.warn_message, ResourceWarning) -class _TemporaryFileWrapper: +class TemporaryFileWrapper: """Temporary file wrapper This class provides a wrapper around files opened for @@ -555,6 +556,19 @@ def __iter__(self): for line in self.file: yield line +def __getattr__(name): + if name == "_TemporaryFileWrapper": + _warnings._deprecated( + "tempfile._TemporaryFileWrapper", + message=( + "{name!r} is deprecated and slated for removal in Python {remove}. " + "Use tempfile.TemporaryFileWrapper instead." + ), + remove=(3, 21), + ) + return TemporaryFileWrapper + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True, *, errors=None, @@ -607,7 +621,7 @@ def opener(*args): raw = getattr(file, 'buffer', file) raw = getattr(raw, 'raw', raw) raw.name = name - return _TemporaryFileWrapper(file, name, delete, delete_on_close) + return TemporaryFileWrapper(file, name, delete, delete_on_close) except: file.close() raise diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index 27bd63bde08eb8b..b5f6603defde5c5 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -31,6 +31,11 @@ def __init__(self, typecode, newarg=None): class MiscTest(unittest.TestCase): + def test_array_type_importable(self): + from array import ArrayType + + self.assertIs(array.array, ArrayType) + def test_array_is_sequence(self): self.assertIsInstance(array.array("B"), collections.abc.MutableSequence) self.assertIsInstance(array.array("B"), collections.abc.Reversible) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index fa3821e0783858c..b4fd7abed5963ea 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1282,6 +1282,47 @@ def getaddrinfo(*args, **kw): self.loop.run_until_complete(coro) self.assertTrue(sock.close.called) + def test_create_connection_sock_transport_error_closes_sock(self): + # gh-153133: a user-provided socket is closed if the transport is + # never created. + sock = mock.Mock() + sock.type = socket.SOCK_STREAM + + def factory(): + raise ZeroDivisionError + + coro = self.loop.create_connection(factory, sock=sock) + with self.assertRaises(ZeroDivisionError): + self.loop.run_until_complete(coro) + self.assertTrue(sock.close.called) + + @patch_socket + def test_create_connection_transport_error_closes_sock(self, m_socket): + # gh-153133: an internally created socket is closed if the transport + # is never created. + sock = mock.Mock() + m_socket.socket.return_value = sock + + def getaddrinfo(*args, **kw): + fut = self.loop.create_future() + addr = (socket.AF_INET, socket.SOCK_STREAM, 0, '', + ('127.0.0.1', 80)) + fut.set_result([addr]) + return fut + self.loop.getaddrinfo = getaddrinfo + + async def sock_connect(sock, address): + return None + + def factory(): + raise ZeroDivisionError + + with mock.patch.object(self.loop, 'sock_connect', sock_connect): + coro = self.loop.create_connection(factory, '127.0.0.1', 80) + with self.assertRaises(ZeroDivisionError): + self.loop.run_until_complete(coro) + self.assertTrue(sock.close.called) + @patch_socket def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket): # See gh-135836: Fix IndexError when Happy Eyeballs algorithm diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 40111d410077950..575a322aa45923d 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1604,19 +1604,24 @@ def dummycallback(sock, servername, ctx, cycle=ctx): gc.collect() self.assertIs(wr(), None) - @unittest.skipUnless(support.Py_GIL_DISABLED, - "test is only useful if the GIL is disabled") @threading_helper.requires_working_threading() def test_sni_callback_race(self): - # Replacing sni_callback while handshakes are in-flight must not + # Replacing sni_callback while a handshake is in-flight must not # crash (use-after-free on the callback in free-threaded builds). + # + # Use a single handshake thread: OpenSSL has internal data races + # on shared SSL_CTX state when multiple handshakes run + # concurrently against the same context (gh-150191). Concurrency + # on the *setter* is what exercises the fix from gh-149816, so + # multiple toggler threads race against each other and against + # the one handshake worker. client_ctx, server_ctx, hostname = testing_context() server_ctx.sni_callback = lambda *a: None - done = threading.Event() + deadline = time.monotonic() + 0.1 def do_handshakes(): - while not done.is_set(): + while time.monotonic() < deadline: c_in = ssl.MemoryBIO() c_out = ssl.MemoryBIO() s_in = ssl.MemoryBIO() @@ -1643,19 +1648,11 @@ def do_handshakes(): c_in.write(s_out.read()) def toggle_callback(): - while not done.is_set(): + while time.monotonic() < deadline: server_ctx.sni_callback = lambda *a: None server_ctx.sni_callback = None - workers = max(4, (os.cpu_count() or 4) * 2) - threads = [threading.Thread(target=do_handshakes) - for _ in range(workers)] - threads.append(threading.Thread(target=toggle_callback)) - - with threading_helper.catch_threading_exception() as cm: - with threading_helper.start_threads(threads): - done.set() - self.assertIsNone(cm.exc_value) + threading_helper.run_concurrently([do_handshakes] + 4 * [toggle_callback]) def test_cert_store_stats(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 3b081ecd4a3aa50..e33cc65e090e3b4 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -148,6 +148,7 @@ def test_exports(self): "template" : 1, "SpooledTemporaryFile" : 1, "TemporaryDirectory" : 1, + "TemporaryFileWrapper" : 1, } unexp = [] @@ -980,12 +981,23 @@ def do_create(self, dir=None, pre="", suf="", delete=True): def test_basic(self): # NamedTemporaryFile can create files - self.do_create() + f = self.do_create() + self.assertIsInstance(f, tempfile.TemporaryFileWrapper) self.do_create(pre="a") self.do_create(suf="b") self.do_create(pre="a", suf="b") self.do_create(pre="aa", suf=".txt") + def test_in_all(self): + self.assertIn("TemporaryFileWrapper", tempfile.__all__) + + def test_deprecated_TemporaryFileWrapper_alias(self): + # gh-152586: _TemporaryFileWrapper is a deprecated alias + # for the public TemporaryFileWrapper class. + with self.assertWarns(DeprecationWarning): + obj = tempfile._TemporaryFileWrapper + self.assertIs(obj, tempfile.TemporaryFileWrapper) + def test_method_lookup(self): # Issue #18879: Looking up a temporary file method should keep it # alive long enough. @@ -1141,7 +1153,7 @@ def my_func(dir): try: with self.assertWarnsRegex( expected_warning=ResourceWarning, - expected_regex=r"Implicitly cleaning up <_TemporaryFileWrapper file=.*>", + expected_regex=r"Implicitly cleaning up ", ): tmp_name = my_func(dir) support.gc_collect() @@ -1185,7 +1197,7 @@ def test_bad_encoding(self): def test_unexpected_error(self): dir = tempfile.mkdtemp() self.addCleanup(os_helper.rmtree, dir) - with mock.patch('tempfile._TemporaryFileWrapper') as mock_ntf, \ + with mock.patch('tempfile.TemporaryFileWrapper') as mock_ntf, \ mock.patch('io.open', mock.mock_open()) as mock_open: mock_ntf.side_effect = KeyboardInterrupt() with self.assertRaises(KeyboardInterrupt): diff --git a/Lib/test/test_urllib_response.py b/Lib/test/test_urllib_response.py index d949fa38bfc42f1..8c4acf4afc845e8 100644 --- a/Lib/test/test_urllib_response.py +++ b/Lib/test/test_urllib_response.py @@ -21,7 +21,7 @@ def setUp(self): def test_with(self): addbase = urllib.response.addbase(self.fp) - self.assertIsInstance(addbase, tempfile._TemporaryFileWrapper) + self.assertIsInstance(addbase, tempfile.TemporaryFileWrapper) def f(): with addbase as spam: diff --git a/Lib/urllib/response.py b/Lib/urllib/response.py index 5a2c3cc78c395d5..fc57ad82d99ac09 100644 --- a/Lib/urllib/response.py +++ b/Lib/urllib/response.py @@ -11,7 +11,7 @@ __all__ = ['addbase', 'addclosehook', 'addinfo', 'addinfourl'] -class addbase(tempfile._TemporaryFileWrapper): +class addbase(tempfile.TemporaryFileWrapper): """Base class for addinfo and addclosehook. Is a good idea for garbage collection.""" # XXX Add a method to expose the timeout on the underlying socket? diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-07-04-16-48.gh-issue-153205.5fE8QZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-07-04-16-48.gh-issue-153205.5fE8QZ.rst new file mode 100644 index 000000000000000..9ccfbff7ee69778 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-07-04-16-48.gh-issue-153205.5fE8QZ.rst @@ -0,0 +1,2 @@ +Fix a potential :exc:`SystemError` during vector calls when memory allocation fails. +A :exc:`MemoryError` is now raised instead. diff --git a/Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst b/Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst new file mode 100644 index 000000000000000..6c800551c906f2e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst @@ -0,0 +1 @@ +:class:`!tempfile._TemporaryFileWrapper` has been renamed to the public :class:`tempfile.TemporaryFileWrapper`. The old private name is kept as a deprecated alias and will be removed in Python 3.21. diff --git a/Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst b/Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst new file mode 100644 index 000000000000000..a7a68158ac895e3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst @@ -0,0 +1,2 @@ +Fix a socket leak in :meth:`asyncio.loop.create_connection` when the +transport cannot be created. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 45aad5d6fe21918..2b1373dbe6eb461 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -3401,6 +3401,12 @@ array_modexec(PyObject *m) CREATE_TYPE(m, state->ArrayIterType, &arrayiter_spec); Py_SET_TYPE(state->ArrayIterType, &PyType_Type); + // Older undocumented alias: + if (PyModule_AddObjectRef(m, "ArrayType", + (PyObject *)state->ArrayType) < 0) { + return -1; + } + PyObject *mutablesequence = PyImport_ImportModuleAttrString( "collections.abc", "MutableSequence"); if (!mutablesequence) { diff --git a/Modules/getpath.c b/Modules/getpath.c index 0c80678a678dc8b..ed41536acbcc813 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -198,57 +198,67 @@ getpath_isdir(PyObject *Py_UNUSED(self), PyObject *args) static PyObject * getpath_isfile(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r = NULL; PyObject *pathobj; - const wchar_t *path; if (!PyArg_ParseTuple(args, "U", &pathobj)) { return NULL; } - path = PyUnicode_AsWideCharString(pathobj, NULL); - if (path) { + + int isfile; #ifdef MS_WINDOWS - DWORD attr = GetFileAttributesW(path); - r = (attr != INVALID_FILE_ATTRIBUTES) && - !(attr & FILE_ATTRIBUTE_DIRECTORY) ? Py_True : Py_False; + wchar_t *path = PyUnicode_AsWideCharString(pathobj, NULL); + if (path == NULL) { + return NULL; + } + + DWORD attr = GetFileAttributesW(path); + PyMem_Free(path); + isfile = ((attr != INVALID_FILE_ATTRIBUTES) + && !(attr & FILE_ATTRIBUTE_DIRECTORY)); #else - struct stat st; - r = (_Py_wstat(path, &st) == 0) && S_ISREG(st.st_mode) ? Py_True : Py_False; -#endif - PyMem_Free((void *)path); + struct stat st; + int res = _Py_stat(pathobj, &st); + if (res == -2) { + return NULL; } - return Py_XNewRef(r); + isfile = ((res == 0) && S_ISREG(st.st_mode)); +#endif + return PyBool_FromLong(isfile); } static PyObject * getpath_isxfile(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r = NULL; PyObject *pathobj; - const wchar_t *path; - Py_ssize_t cchPath; if (!PyArg_ParseTuple(args, "U", &pathobj)) { return NULL; } - path = PyUnicode_AsWideCharString(pathobj, &cchPath); - if (path) { + + int isxfile; #ifdef MS_WINDOWS - DWORD attr = GetFileAttributesW(path); - r = (attr != INVALID_FILE_ATTRIBUTES) && - !(attr & FILE_ATTRIBUTE_DIRECTORY) && - (cchPath >= 4) && - (CompareStringOrdinal(path + cchPath - 4, -1, L".exe", -1, 1 /* ignore case */) == CSTR_EQUAL) - ? Py_True : Py_False; + Py_ssize_t cchPath; + wchar_t *path = PyUnicode_AsWideCharString(pathobj, &cchPath); + if (path == NULL) { + return NULL; + } + + DWORD attr = GetFileAttributesW(path); + PyMem_Free(path); + isxfile = (attr != INVALID_FILE_ATTRIBUTES) && + !(attr & FILE_ATTRIBUTE_DIRECTORY) && + (cchPath >= 4) && + (CompareStringOrdinal(path + cchPath - 4, -1, L".exe", -1, 1 /* ignore case */) == CSTR_EQUAL); #else - struct stat st; - r = (_Py_wstat(path, &st) == 0) && - S_ISREG(st.st_mode) && - (st.st_mode & 0111) - ? Py_True : Py_False; -#endif - PyMem_Free((void *)path); + struct stat st; + int res = _Py_stat(pathobj, &st); + if (res == -2) { + return NULL; } - return Py_XNewRef(r); + isxfile = ((res == 0) + && S_ISREG(st.st_mode) + && (st.st_mode & 0111)); +#endif + return PyBool_FromLong(isxfile); } @@ -340,25 +350,16 @@ getpath_joinpath(PyObject *Py_UNUSED(self), PyObject *args) static PyObject * getpath_readlines(PyObject *Py_UNUSED(self), PyObject *args) { - PyObject *r = NULL; PyObject *pathobj; - const wchar_t *path; if (!PyArg_ParseTuple(args, "U", &pathobj)) { return NULL; } - path = PyUnicode_AsWideCharString(pathobj, NULL); - if (!path) { - return NULL; - } - FILE *fp = _Py_wfopen(path, L"rb"); + FILE *fp = Py_fopen(pathobj, "rb"); if (!fp) { - PyErr_SetFromErrno(PyExc_OSError); - PyMem_Free((void *)path); return NULL; } - PyMem_Free((void *)path); - r = PyList_New(0); + PyObject *r = PyList_New(0); if (!r) { fclose(fp); return NULL; diff --git a/Python/ceval.c b/Python/ceval.c index f3f03b28112137a..3f636c3416e1207 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1056,6 +1056,7 @@ _PyObjectArray_FromStackRefArray(_PyStackRef *input, Py_ssize_t nargs, PyObject // +1 in case PY_VECTORCALL_ARGUMENTS_OFFSET is set. result = PyMem_Malloc((nargs + 1) * sizeof(PyObject *)); if (result == NULL) { + PyErr_NoMemory(); return NULL; } } diff --git a/Python/compile.c b/Python/compile.c index f2c1de5e0c07c63..fefb2b04b78db86 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1689,7 +1689,7 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags, metadata = PyDict_New(); if (metadata == NULL) { - return NULL; + goto finally; } if (compiler_codegen(c, mod) < 0) { diff --git a/Python/fileutils.c b/Python/fileutils.c index 0c1766b88045002..98ed66eff94ee10 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -1368,22 +1368,21 @@ _Py_stat(PyObject *path, struct stat *statbuf) PyMem_Free(wpath); return err; #else - int ret; - PyObject *bytes; - char *cpath; - - bytes = PyUnicode_EncodeFSDefault(path); - if (bytes == NULL) + PyObject *bytes = PyUnicode_EncodeFSDefault(path); + if (bytes == NULL) { return -2; + } /* check for embedded null bytes */ + char *cpath; if (PyBytes_AsStringAndSize(bytes, &cpath, NULL) == -1) { Py_DECREF(bytes); return -2; } - ret = stat(cpath, statbuf); + int ret = stat(cpath, statbuf); Py_DECREF(bytes); + assert(ret == 0 || ret == -1); return ret; #endif }