diff --git a/Doc/library/asyncio-stream.rst b/Doc/library/asyncio-stream.rst index 885d463c0fca898..d75846163edfddb 100644 --- a/Doc/library/asyncio-stream.rst +++ b/Doc/library/asyncio-stream.rst @@ -49,7 +49,7 @@ and work with streams: .. function:: open_connection(host=None, port=None, *, \ - limit=None, ssl=None, family=0, proto=0, \ + limit=65536, ssl=None, family=0, proto=0, \ flags=0, sock=None, local_addr=None, \ server_hostname=None, ssl_handshake_timeout=None, \ ssl_shutdown_timeout=None, \ @@ -89,7 +89,7 @@ and work with streams: .. function:: start_server(client_connected_cb, host=None, \ - port=None, *, limit=None, \ + port=None, *, limit=65536, \ family=socket.AF_UNSPEC, \ flags=socket.AI_PASSIVE, sock=None, \ backlog=100, ssl=None, reuse_address=None, \ @@ -137,7 +137,7 @@ and work with streams: .. rubric:: Unix Sockets -.. function:: open_unix_connection(path=None, *, limit=None, \ +.. function:: open_unix_connection(path=None, *, limit=65536, \ ssl=None, sock=None, server_hostname=None, \ ssl_handshake_timeout=None, ssl_shutdown_timeout=None) :async: @@ -169,7 +169,7 @@ and work with streams: .. function:: start_unix_server(client_connected_cb, path=None, \ - *, limit=None, sock=None, backlog=100, ssl=None, \ + *, limit=65536, sock=None, backlog=100, ssl=None, \ ssl_handshake_timeout=None, \ ssl_shutdown_timeout=None, start_serving=True, \ cleanup_socket=True, mode=None) diff --git a/Doc/library/asyncio-subprocess.rst b/Doc/library/asyncio-subprocess.rst index 70f711b779edf85..2fdc10db5bfd51e 100644 --- a/Doc/library/asyncio-subprocess.rst +++ b/Doc/library/asyncio-subprocess.rst @@ -62,7 +62,7 @@ Creating Subprocesses ===================== .. function:: create_subprocess_exec(program, *args, stdin=None, \ - stdout=None, stderr=None, limit=None, **kwds) + stdout=None, stderr=None, limit=65536, **kwds) :async: Create a subprocess. @@ -84,7 +84,7 @@ Creating Subprocesses .. function:: create_subprocess_shell(cmd, stdin=None, \ - stdout=None, stderr=None, limit=None, **kwds) + stdout=None, stderr=None, limit=65536, **kwds) :async: Run the *cmd* shell command. diff --git a/Doc/library/zlib.rst b/Doc/library/zlib.rst index f043915c0f4b94e..266bf968e329b4a 100644 --- a/Doc/library/zlib.rst +++ b/Doc/library/zlib.rst @@ -27,7 +27,7 @@ The available exception and functions in this module are: Exception raised on compression and decompression errors. -.. function:: adler32(data[, value]) +.. function:: adler32(data, value=1, /) Computes an Adler-32 checksum of *data*. (An Adler-32 checksum is almost as reliable as a CRC32 but can be computed much more quickly.) The result @@ -127,7 +127,7 @@ The available exception and functions in this module are: Added the *zdict* parameter and keyword argument support. -.. function:: crc32(data[, value]) +.. function:: crc32(data, value=0, /) .. index:: single: Cyclic Redundancy Check @@ -205,7 +205,7 @@ The available exception and functions in this module are: .. versionchanged:: 3.6 *wbits* and *bufsize* can be used as keyword arguments. -.. function:: decompressobj(wbits=MAX_WBITS[, zdict]) +.. function:: decompressobj(wbits=MAX_WBITS, zdict=b'') Returns a decompression object, to be used for decompressing data streams that won't fit into memory at once. @@ -231,7 +231,7 @@ The available exception and functions in this module are: Compression objects support the following methods: -.. method:: Compress.compress(data) +.. method:: Compress.compress(data, /) Compress *data*, returning a bytes object containing compressed data for at least part of the data in *data*. This data should be concatenated to the output @@ -239,7 +239,7 @@ Compression objects support the following methods: be kept in internal buffers for later processing. -.. method:: Compress.flush([mode]) +.. method:: Compress.flush(mode=Z_FINISH, /) All pending input is processed, and a bytes object containing the remaining compressed output is returned. *mode* can be selected from the constants @@ -294,7 +294,7 @@ Decompression objects support the following methods and attributes: .. versionadded:: 3.3 -.. method:: Decompress.decompress(data, max_length=0) +.. method:: Decompress.decompress(data, /, max_length=0) Decompress *data*, returning a bytes object containing the uncompressed data corresponding to at least part of the data in *string*. This data should be @@ -318,7 +318,7 @@ Decompression objects support the following methods and attributes: *max_length* can be used as a keyword argument. -.. method:: Decompress.flush([length]) +.. method:: Decompress.flush(length=DEF_BUF_SIZE, /) All pending input is processed, and a bytes object containing the remaining uncompressed output is returned. After calling :meth:`flush`, the diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 83916160b9fbde9..16d5c1b6f0a3e19 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -253,7 +253,9 @@ async def _accept_connection2( except (SystemExit, KeyboardInterrupt): raise except BaseException as exc: - if self._debug: + if transport is None: + conn.close() + if transport is None or self._debug: context = { 'message': 'Error on transport creation for incoming connection', diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 1b77d6d317e9375..955e8e677eac1a5 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -116,6 +116,7 @@ async def _aexit(self, et, exc): # can be cancelled multiple times if our parent task # is being cancelled repeatedly (or even once, when # our own cancellation is already in progress) + pending_cancellation_error = None while self._tasks: if self._on_completed_fut is None: self._on_completed_fut = self._loop.create_future() @@ -123,6 +124,7 @@ async def _aexit(self, et, exc): try: await self._on_completed_fut except exceptions.CancelledError as ex: + pending_cancellation_error = ex if not self._aborting: # Our parent task is being cancelled: # @@ -163,6 +165,9 @@ async def _aexit(self, et, exc): # If there are no pending cancellations left, # don't propagate CancelledError. propagate_cancellation_error = None + elif propagate_cancellation_error is None: + # gh-155433: the remaining cancellation is not ours, don't drop it + propagate_cancellation_error = pending_cancellation_error # Propagate CancelledError if there is one, except if there # are other errors -- those have priority. diff --git a/Lib/idlelib/idle_test/README.txt b/Lib/idlelib/idle_test/README.txt index cacd06db873d039..242de2225248178 100644 --- a/Lib/idlelib/idle_test/README.txt +++ b/Lib/idlelib/idle_test/README.txt @@ -33,9 +33,9 @@ insert the import and main lines before the htest lines. if __name__ == "__main__": from unittest import main - main('idlelib.idle_test.test_abc', verbosity=2, exit=False) + main('idlelib.idle_test.test_abc', verbosity=2) -The ', exit=False' is only needed if an htest follows. +Add ', exit=False' to the main call if and only if an htest follows. diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index 778e5c3d84e4963..0bd0378fbfa07a9 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -1,48 +1,51 @@ """Run human tests of Idle's window, dialog, and popup widgets. -run(*tests) Create a master Tk() htest window. Within that, run each -callable in tests after finding the matching test spec in this file. If -tests is empty, run an htest for each spec dict in this file after -finding the matching callable in the module named in the spec. Close -the master window to end testing. - -In a tested module, let X be a global name bound to a callable (class or -function) whose .__name__ attribute is also X (the usual situation). The -first parameter of X must be 'parent' or 'master'. When called, the -first argument will be the root window. X must create a child -Toplevel(parent/master) (or subclass thereof). The Toplevel may be a -test widget or dialog, in which case the callable is the corresponding -class. Or the Toplevel may contain the widget to be tested or set up a -context in which a test widget is invoked. In this latter case, the -callable is a wrapper function that sets up the Toplevel and other -objects. Wrapper function names, such as _editor_window', should start -with '_' and be lowercase. - +The main function, `run(*tests)`, is defined at the end of this file. +Argument `tests` is a possibly empty tuple of callables defined in some +idlelib.abc module (or possibly modules). Its steps: +1. Create a master Tk() htest window. Within that window ... +2a. If tuple `tests` is not empty, run was likely called from one + module. Run each callable in `tests` after finding the matching + callable_spec test spec in this file. +2b. If tests is empty, run was likely called from this file. + Run an htest for each spec dict in this file after finding the + matching callable in the module named in the spec. +3. Close the master window to end testing. + +In a tested module, let X be a global name bound to a callable (class +or function) whose .__name__ attribute (its `class` or `def` definition +name) is also X. X must expect exactly 1 positional argument, a +parent toplevel window. Run passes the htest window. X must create a +child Toplevel(parent/master). The callable may be either a runtime +object or a wrapper function written just for the test. In the latter +case, its name should start with '_' and be lowercase (such as '_ttt'). End the module with - +``` if __name__ == '__main__': - + from unittest import main + main("idlelib.idle_test.test_xyz", verbosity=2, exit=False) + from idlelib.idle_test.htest import run - run(callable) # There could be multiple comma-separated callables. + run(callable) +``` +Replace 'xyz' as appropriate and 'callable' with the callable name or +comma-separated names (multiple names is rare). 'exit=False' is needed +for the htest to run. To have wrapper functions ignored by coverage reports, tag the def -header like so: "def _wrapper(parent): # htest #". Use the same tag -for htest lines in widget code. Make sure that the 'if __name__' line -matches the above. Then have make sure that .coveragerc includes the -following: - +header like so: "def _wrapper(root): # htest #". Use the same tag +for htest-only lines in the main code. To ignore the 'if __name__' +statement, match the example above. Add the below to coveragerc. +``` [report] exclude_lines = .*# htest # if __name__ == .__main__.: - -(The "." instead of "'" is intentional and necessary.) - +``` To run any X, this file must contain a matching instance of the following template, with X.__name__ prepended to '_spec'. -When all tests are run, the prefix is use to get X. callable_spec = { 'file': '', @@ -51,11 +54,10 @@ } file (no .py): run() imports file.py. -kwds: augmented with {'parent':root} and passed to X as **kwds. +kwds: run() augments with {'parent':root} and passes to X as **kwds. title: an example kwd; some widgets need this, delete line if not. msg: master window hints about testing the widget. - TODO test these modules and classes: autocomplete_w.AutoCompleteWindow debugger.Debugger diff --git a/Lib/idlelib/idle_test/test_delegator.py b/Lib/idlelib/idle_test/test_delegator.py index 922416297a42e02..c4273deee7ffbdd 100644 --- a/Lib/idlelib/idle_test/test_delegator.py +++ b/Lib/idlelib/idle_test/test_delegator.py @@ -41,4 +41,4 @@ def test_mydel(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=2) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_format.py b/Lib/idlelib/idle_test/test_format.py index e5e903688597aa7..6550e9765f290d3 100644 --- a/Lib/idlelib/idle_test/test_format.py +++ b/Lib/idlelib/idle_test/test_format.py @@ -665,4 +665,4 @@ def test_rstrip_end(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=2) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_history.py b/Lib/idlelib/idle_test/test_history.py index 675396514447514..e1031579c3d8210 100644 --- a/Lib/idlelib/idle_test/test_history.py +++ b/Lib/idlelib/idle_test/test_history.py @@ -169,4 +169,4 @@ def test_history_prev_next(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=2) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_pathbrowser.py b/Lib/idlelib/idle_test/test_pathbrowser.py index 13d8b9e1ba9572a..a198978d5c1ef72 100644 --- a/Lib/idlelib/idle_test/test_pathbrowser.py +++ b/Lib/idlelib/idle_test/test_pathbrowser.py @@ -83,4 +83,4 @@ def test_PathBrowserTreeItem(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_query.py b/Lib/idlelib/idle_test/test_query.py index a6ef858a8c954a2..58c173723a5adac 100644 --- a/Lib/idlelib/idle_test/test_query.py +++ b/Lib/idlelib/idle_test/test_query.py @@ -448,4 +448,4 @@ def test_click_args(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_search.py b/Lib/idlelib/idle_test/test_search.py index de703c195cd2290..2b0a9d483bfc0ff 100644 --- a/Lib/idlelib/idle_test/test_search.py +++ b/Lib/idlelib/idle_test/test_search.py @@ -77,4 +77,4 @@ def test_find_selection(self): text.delete('2.0', 'end') if __name__ == '__main__': - unittest.main(verbosity=2, exit=2) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_searchbase.py b/Lib/idlelib/idle_test/test_searchbase.py index 8c9c410ebaf47c0..1780cab6527dd94 100644 --- a/Lib/idlelib/idle_test/test_searchbase.py +++ b/Lib/idlelib/idle_test/test_searchbase.py @@ -157,4 +157,4 @@ def test_create_command_buttons(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=2) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_text.py b/Lib/idlelib/idle_test/test_text.py index 43a9ba02c3d3c9a..8ee1c9f2d768131 100644 --- a/Lib/idlelib/idle_test/test_text.py +++ b/Lib/idlelib/idle_test/test_text.py @@ -233,4 +233,4 @@ def setUp(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_undo.py b/Lib/idlelib/idle_test/test_undo.py index beb5b582039f884..0488a2c9809b48c 100644 --- a/Lib/idlelib/idle_test/test_undo.py +++ b/Lib/idlelib/idle_test/test_undo.py @@ -132,4 +132,4 @@ def test_addcmd(self): if __name__ == '__main__': - unittest.main(verbosity=2, exit=False) + unittest.main(verbosity=2) diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index cf46c13fa5e1f39..a323084d262ebfe 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -421,6 +421,60 @@ def test_accept_connection_reschedules_once_on_resource_error(self): self.assertEqual(self.loop.call_exception_handler.call_count, 1) self.assertEqual(self.loop.call_later.call_count, 1) + def test_accept_connection2_factory_error_closes_conn(self): + # gh-155934: if the transport was never created, the accepted + # socket is closed and the error is reported even when debug + # mode is disabled. + self.loop.set_debug(False) + conn = mock.Mock() + + def factory(): + raise RuntimeError("protocol_factory failed") + + self.loop.call_exception_handler = mock.Mock() + self.loop.run_until_complete( + self.loop._accept_connection2(factory, conn, {})) + + self.assertTrue(conn.close.called) + self.loop.call_exception_handler.assert_called_once() + + def test_accept_connection2_transport_error_closes_conn(self): + # gh-155934: same when the transport creation itself fails. + self.loop.set_debug(False) + conn = mock.Mock() + self.loop._make_socket_transport = mock.Mock( + side_effect=ZeroDivisionError) + self.loop.call_exception_handler = mock.Mock() + + self.loop.run_until_complete( + self.loop._accept_connection2(mock.Mock(), conn, {})) + + self.assertTrue(conn.close.called) + self.loop.call_exception_handler.assert_called_once() + + def test_accept_connection2_waiter_error_stays_debug_only(self): + # Once the transport exists it owns the socket: waiter failures + # (e.g. SSL handshake errors) close the transport and stay + # debug-only, and the accepted socket is not closed directly. + self.loop.set_debug(False) + conn = mock.Mock() + transport = mock.Mock() + + def make_transport(conn, protocol, waiter=None, **kwargs): + waiter.set_exception(OSError("handshake failed")) + return transport + + self.loop._make_socket_transport = make_transport + self.loop.call_exception_handler = mock.Mock() + + self.loop.run_until_complete( + self.loop._accept_connection2(mock.Mock(), conn, {})) + + self.assertTrue(transport.close.called) + self.assertFalse(conn.close.called) + self.assertFalse(self.loop.call_exception_handler.called) + + class SelectorTransportTests(test_utils.TestCase): def setUp(self): diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index f5b7daf9e2ce1d0..983e1a7dc53e6f5 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1187,6 +1187,27 @@ async def test_taskgroup_cancel_before_create_task(self): with self.assertRaises(RuntimeError): tg.create_task(asyncio.sleep(1)) + async def test_taskgroup_cancel_keeps_outer_cancellation(self): + # gh-155433: any cancellation from outside the group must propagate. + async def child(): + try: + await asyncio.sleep(10) + finally: + await asyncio.sleep(0.1) + + async def body(): + async with asyncio.TaskGroup() as tg: + tg.create_task(child()) + await asyncio.sleep(0) + tg.cancel() + + task = asyncio.create_task(body()) + await asyncio.sleep(0.01) + task.cancel('message') + with self.assertRaises(asyncio.CancelledError) as cm: + await task + self.assertEqual('message', cm.exception.args[0]) + async def test_taskgroup_cancel_before_exception(self): async def raise_exc(parent_tg: asyncio.TaskGroup): parent_tg.cancel() diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 25d6d1a248b4577..555cebeadc60538 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1267,6 +1267,24 @@ def test_import_time(self): assert_python_failure('-X', 'importtime=-1', '-c', code) assert_python_failure('-X', 'importtime=3', '-c', code) + def test_import_time_unencodable_module_name(self): + code = textwrap.dedent(""" + import sys, types + name = 'mod\\ud800' + sys.modules[name] = types.ModuleType(name) + __import__(name) + try: + __import__('nonexistent\\ud800') + except ModuleNotFoundError: + pass + """) + res = assert_python_ok('-X', 'importtime=2', '-c', code) + res_err = res.err.decode('utf-8') + self.assertRegex(res_err, + r'import time: cached\s* \| cached\s* \| mod\\ud800') + self.assertRegex(res_err, + r'import time: \s*\d+ \| \s*\d+ \| \s*nonexistent\\ud800') + def res2int(self, res): out = res.out.strip().decode("utf-8") return tuple(int(i) for i in out.split()) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index 70d1cd81ac6c46d..024c58e7cabd79d 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -722,6 +722,20 @@ def test_decompress_eof_incomplete_stream(self): dco.flush() self.assertFalse(dco.eof) + def test_decompress_flush_corrupt_stream(self): + x = b'x\x9cK\xcb\xcf\x07\x00\x02\x82\x01E' # 'foo' + corrupt = x[:-1] + b'\x00' + dco = zlib.decompressobj() + self.assertEqual(dco.decompress(corrupt, 1), b'f') + self.assertRaises(zlib.error, dco.flush) + + def test_decompress_flush_twice(self): + x = b'x\x9cK\xcb\xcf\x07\x00\x02\x82\x01E' # 'foo' + dco = zlib.decompressobj() + self.assertEqual(dco.decompress(x), b'foo') + self.assertEqual(dco.flush(), b'') + self.assertEqual(dco.flush(), b'') + def test_decompress_unused_data(self): # Repeated calls to decompress() after EOF should accumulate data in # dco.unused_data, instead of just storing the arg to the last call. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-20-22-13-20.gh-issue-156126.pXm4Qr.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-20-22-13-20.gh-issue-156126.pXm4Qr.rst new file mode 100644 index 000000000000000..d8cfefb86b06dd2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-20-22-13-20.gh-issue-156126.pXm4Qr.rst @@ -0,0 +1,3 @@ +Fix a crash when importing a module whose name contains characters that +cannot be encoded to UTF-8 (such as lone surrogates) while :option:`-X +importtime <-X>` is enabled. diff --git a/Misc/NEWS.d/next/Library/2026-08-09-16-14-38.gh-issue-155433.KL7tHV.rst b/Misc/NEWS.d/next/Library/2026-08-09-16-14-38.gh-issue-155433.KL7tHV.rst new file mode 100644 index 000000000000000..dc8961e976abbc2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-09-16-14-38.gh-issue-155433.KL7tHV.rst @@ -0,0 +1,2 @@ +Fix :class:`asyncio.TaskGroup` losing outside cancellation after +``cancel()``. diff --git a/Misc/NEWS.d/next/Library/2026-08-17-18-00-00.gh-issue-155934.acCept.rst b/Misc/NEWS.d/next/Library/2026-08-17-18-00-00.gh-issue-155934.acCept.rst new file mode 100644 index 000000000000000..cc949bab5cb0bb5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-17-18-00-00.gh-issue-155934.acCept.rst @@ -0,0 +1,3 @@ +Fix a socket leak in :mod:`asyncio` when transport creation fails for a +connection accepted by a server, and report the error via the loop exception +handler even when debug mode is disabled. diff --git a/Misc/NEWS.d/next/Library/2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst b/Misc/NEWS.d/next/Library/2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst new file mode 100644 index 000000000000000..846931a1cab5117 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-21-11-56-28.gh-issue-156173.mhZa8a.rst @@ -0,0 +1,2 @@ +Calling :meth:`zlib.Decompress.flush` on invalid compressed data now +raises :exc:`zlib.error` instead of being silently ignored. diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index d06b94d1e83713c..a7fefb1fec5de93 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -1271,6 +1271,13 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, PyMutex_Lock(&self->mutex); + /* A previous flush() already reached the end of the stream and freed the + decompression state, so there is nothing left to process. */ + if (!self->is_initialised) { + PyMutex_Unlock(&self->mutex); + return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES); + } + if (PyObject_GetBuffer(self->unconsumed_tail, &data, PyBUF_SIMPLE) == -1) { PyMutex_Unlock(&self->mutex); return NULL; @@ -1328,6 +1335,10 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, goto abort; } } + else if (err != Z_OK && err != Z_BUF_ERROR) { + zlib_error(state, self->zst, err, "while decompressing data"); + goto abort; + } return_value = OutputBuffer_WindowFinish(&buffer, &window, self->zst.avail_out); if (return_value != NULL) { diff --git a/Python/import.c b/Python/import.c index 47d5296a2fec9b1..037f15d4ca2bafa 100644 --- a/Python/import.c +++ b/Python/import.c @@ -286,6 +286,19 @@ _PyImport_ClearLazyModules(PyInterpreterState *interp) Py_CLEAR(LAZY_PENDING_SUBMODULES(interp)); } +static PyObject * +get_importtime_name(PyObject *name) +{ + PyObject *exc = PyErr_GetRaisedException(); + PyObject *encoded = PyUnicode_AsEncodedString(name, "utf-8", + "backslashreplace"); + if (encoded == NULL) { + PyErr_Clear(); + } + PyErr_SetRaisedException(exc); + return encoded; +} + static int import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *name) { @@ -323,8 +336,11 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n if (_PyInterpreterState_GetConfig(interp)->import_time == 2) { _IMPORT_TIME_HEADER(interp); #define import_level FIND_AND_LOAD(interp).import_level + PyObject *encoded_name = get_importtime_name(name); fprintf(stderr, "import time: cached | cached | %*s\n", - import_level*2, PyUnicode_AsUTF8(name)); + import_level*2, + encoded_name != NULL ? PyBytes_AS_STRING(encoded_name) : "?"); + Py_XDECREF(encoded_name); #undef import_level } @@ -4121,10 +4137,13 @@ import_find_and_load_with_name(PyThreadState *tstate, PyObject *abs_name, PyTime_t cum = t2 - t1; import_level--; + PyObject *encoded_name = get_importtime_name(abs_name); fprintf(stderr, "import time: %9ld | %10ld | %*s%s\n", (long)_PyTime_AsMicroseconds(cum - accumulated, _PyTime_ROUND_CEILING), (long)_PyTime_AsMicroseconds(cum, _PyTime_ROUND_CEILING), - import_level*2, "", PyUnicode_AsUTF8(abs_name)); + import_level*2, "", + encoded_name != NULL ? PyBytes_AS_STRING(encoded_name) : "?"); + Py_XDECREF(encoded_name); accumulated = accumulated_copy + cum; }