From 3da4e0910c23309121e083718a1513847bc36d7a Mon Sep 17 00:00:00 2001
From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Date: Tue, 7 Apr 2026 20:46:24 +0300
Subject: [PATCH 001/129] Post 3.14.4
---
Include/patchlevel.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Include/patchlevel.h b/Include/patchlevel.h
index ba0a2745774daf6..a9f72493bdea71f 100644
--- a/Include/patchlevel.h
+++ b/Include/patchlevel.h
@@ -24,7 +24,7 @@
#define PY_RELEASE_SERIAL 0
/* Version as a string */
-#define PY_VERSION "3.14.4"
+#define PY_VERSION "3.14.4+"
/*--end constants--*/
From 5df7652859c8a303a02101e0496ae56e839b3269 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Tue, 7 Apr 2026 23:37:46 +0200
Subject: [PATCH 002/129] [3.14] gh-146458: Fix REPL height and width tracking
on resize (GH-146459) (#148232)
gh-146458: Fix REPL height and width tracking on resize (GH-146459)
(cherry picked from commit 0b20bff386141ee0e8c62da8366f674bad17e048)
Co-authored-by: Gabriel Volles Marinho <147559808+GabrielvMarinho@users.noreply.github.com>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Victor Stinner
---
Lib/_pyrepl/reader.py | 1 +
Lib/_pyrepl/unix_console.py | 1 -
Lib/test/test_pyrepl/support.py | 2 ++
Lib/test/test_pyrepl/test_reader.py | 1 +
Lib/test/test_pyrepl/test_unix_console.py | 6 ++----
Lib/test/test_pyrepl/test_windows_console.py | 14 ++++----------
.../2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst | 1 +
7 files changed, 11 insertions(+), 15 deletions(-)
create mode 100644 Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst
diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py
index 9ab92f64d1ef63d..f0116e742d2398e 100644
--- a/Lib/_pyrepl/reader.py
+++ b/Lib/_pyrepl/reader.py
@@ -644,6 +644,7 @@ def update_screen(self) -> None:
def refresh(self) -> None:
"""Recalculate and refresh the screen."""
+ self.console.height, self.console.width = self.console.getheightwidth()
# this call sets up self.cxy, so call it first.
self.screen = self.calc_screen()
self.console.refresh(self.screen, self.cxy)
diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py
index 937b5df6ff7d4c3..639d16db3f88d41 100644
--- a/Lib/_pyrepl/unix_console.py
+++ b/Lib/_pyrepl/unix_console.py
@@ -776,7 +776,6 @@ def __move_tall(self, x, y):
self.__write_code(self._cup, y - self.__offset, x)
def __sigwinch(self, signum, frame):
- self.height, self.width = self.getheightwidth()
self.event_queue.insert(Event("resize", None))
def __hide_cursor(self):
diff --git a/Lib/test/test_pyrepl/support.py b/Lib/test/test_pyrepl/support.py
index 4f7f9d77933336f..307bf4505550d61 100644
--- a/Lib/test/test_pyrepl/support.py
+++ b/Lib/test/test_pyrepl/support.py
@@ -88,6 +88,8 @@ def prepare_console(events: Iterable[Event], **kwargs) -> MagicMock | Console:
console.get_event.side_effect = events
console.height = 100
console.width = 80
+ console.getheightwidth = MagicMock(side_effect=lambda: (console.height, console.width))
+
for key, val in kwargs.items():
setattr(console, key, val)
return console
diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py
index b1b6ae16a1e592c..fbf557115f8a254 100644
--- a/Lib/test/test_pyrepl/test_reader.py
+++ b/Lib/test/test_pyrepl/test_reader.py
@@ -228,6 +228,7 @@ def _prepare_console(events):
console.get_event.side_effect = events
console.height = 100
console.width = 80
+ console.getheightwidth = MagicMock(side_effect=lambda: (console.height, console.width))
console.input_hook = input_hook
return console
diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py
index a1ee6d4878fe93b..8198d489188f1e1 100644
--- a/Lib/test/test_pyrepl/test_unix_console.py
+++ b/Lib/test/test_pyrepl/test_unix_console.py
@@ -250,8 +250,7 @@ def test_resize_bigger_on_multiline_function(self, _os_write):
events = itertools.chain(code_to_events(code))
reader, console = handle_events_short_unix_console(events)
- console.height = 2
- console.getheightwidth = MagicMock(lambda _: (2, 80))
+ console.getheightwidth = MagicMock(side_effect=lambda: (2, 80))
def same_reader(_):
return reader
@@ -286,8 +285,7 @@ def test_resize_smaller_on_multiline_function(self, _os_write):
events = itertools.chain(code_to_events(code))
reader, console = handle_events_unix_console_height_3(events)
- console.height = 1
- console.getheightwidth = MagicMock(lambda _: (1, 80))
+ console.getheightwidth = MagicMock(side_effect=lambda: (1, 80))
def same_reader(_):
return reader
diff --git a/Lib/test/test_pyrepl/test_windows_console.py b/Lib/test/test_pyrepl/test_windows_console.py
index 3587b834f3cd077..518ddee11253970 100644
--- a/Lib/test/test_pyrepl/test_windows_console.py
+++ b/Lib/test/test_pyrepl/test_windows_console.py
@@ -115,9 +115,7 @@ def test_resize_wider(self):
events = code_to_events(code)
reader, console = self.handle_events_narrow(events)
- console.height = 20
- console.width = 80
- console.getheightwidth = MagicMock(lambda _: (20, 80))
+ console.getheightwidth = MagicMock(side_effect=lambda: (20, 80))
def same_reader(_):
return reader
@@ -143,9 +141,7 @@ def test_resize_narrower(self):
events = code_to_events(code)
reader, console = self.handle_events(events)
- console.height = 20
- console.width = 4
- console.getheightwidth = MagicMock(lambda _: (20, 4))
+ console.getheightwidth = MagicMock(side_effect=lambda: (20, 4))
def same_reader(_):
return reader
@@ -278,8 +274,7 @@ def test_resize_bigger_on_multiline_function(self):
events = itertools.chain(code_to_events(code))
reader, console = self.handle_events_short(events)
- console.height = 2
- console.getheightwidth = MagicMock(lambda _: (2, 80))
+ console.getheightwidth = MagicMock(side_effect=lambda: (2, 80))
def same_reader(_):
return reader
@@ -316,8 +311,7 @@ def test_resize_smaller_on_multiline_function(self):
events = itertools.chain(code_to_events(code))
reader, console = self.handle_events_height_3(events)
- console.height = 1
- console.getheightwidth = MagicMock(lambda _: (1, 80))
+ console.getheightwidth = MagicMock(side_effect=lambda: (1, 80))
def same_reader(_):
return reader
diff --git a/Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst b/Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst
new file mode 100644
index 000000000000000..178c04c657ecbbd
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst
@@ -0,0 +1 @@
+Fix incorrect REPL height and width tracking on console window resize on Windows.
From a84e2dba24286d9cf13c29807d6994a13dc102a1 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Wed, 8 Apr 2026 19:55:20 +0200
Subject: [PATCH 003/129] [3.14] Minor edit: Four space indent in example
(gh-148264) (gh-148265)
---
Doc/library/itertools.rst | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst
index 53f4b31e17d52bb..1be8406bbdf0c14 100644
--- a/Doc/library/itertools.rst
+++ b/Doc/library/itertools.rst
@@ -853,7 +853,7 @@ and :term:`generators ` which incur interpreter overhead.
def running_mean(iterable):
"Yield the average of all values seen so far."
- # running_mean([8.5, 9.5, 7.5, 6.5]) -> 8.5 9.0 8.5 8.0
+ # running_mean([8.5, 9.5, 7.5, 6.5]) → 8.5 9.0 8.5 8.0
return map(truediv, accumulate(iterable), count(1))
def repeatfunc(function, times=None, *args):
@@ -935,10 +935,10 @@ and :term:`generators ` which incur interpreter overhead.
yield element
def unique(iterable, key=None, reverse=False):
- "Yield unique elements in sorted order. Supports unhashable inputs."
- # unique([[1, 2], [3, 4], [1, 2]]) → [1, 2] [3, 4]
- sequenced = sorted(iterable, key=key, reverse=reverse)
- return unique_justseen(sequenced, key=key)
+ "Yield unique elements in sorted order. Supports unhashable inputs."
+ # unique([[1, 2], [3, 4], [1, 2]]) → [1, 2] [3, 4]
+ sequenced = sorted(iterable, key=key, reverse=reverse)
+ return unique_justseen(sequenced, key=key)
def sliding_window(iterable, n):
"Collect data into overlapping fixed-length chunks or blocks."
From f46a17b19ff535591b75f5f3df5b48f7b4f939b4 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 9 Apr 2026 00:14:15 +0200
Subject: [PATCH 004/129] [3.14] gh-70039: smtplib: store the server name in
._host in .connect() (GH-115259) (#148273)
Original patch by gigaplastik, extended with a few more tests.
Addresses gh-70039 and bpo-25852: failure of starttls if connect is called explicitly.
(cherry picked from commit 442f83a5ea1b4d334befd231a79c40d6ff41a0bd)
Co-authored-by: nmartensen
---
Lib/smtplib.py | 2 +-
Lib/test/test_smtpnet.py | 53 +++++++++++++++++++
...4-02-10-21-25-22.gh-issue-70039.6wvcAP.rst | 1 +
3 files changed, 55 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 72093f7f8b0f2dd..4cfc2338d99c67e 100644
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -251,7 +251,6 @@ def __init__(self, host='', port=0, local_hostname=None,
will be used.
"""
- self._host = host
self.timeout = timeout
self.esmtp_features = {}
self.command_encoding = 'ascii'
@@ -342,6 +341,7 @@ def connect(self, host='localhost', port=0, source_address=None):
port = int(port)
except ValueError:
raise OSError("nonnumeric port")
+ self._host = host
if not port:
port = self.default_port
sys.audit("smtplib.connect", self, host, port)
diff --git a/Lib/test/test_smtpnet.py b/Lib/test/test_smtpnet.py
index d765746987bc4b0..861e7e6a725c407 100644
--- a/Lib/test/test_smtpnet.py
+++ b/Lib/test/test_smtpnet.py
@@ -45,6 +45,59 @@ def test_connect_starttls(self):
server.ehlo()
server.quit()
+ def test_connect_host_port_starttls(self):
+ support.get_attribute(smtplib, 'SMTP_SSL')
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ context.check_hostname = False
+ context.verify_mode = ssl.CERT_NONE
+ with socket_helper.transient_internet(self.testServer):
+ server = smtplib.SMTP(f'{self.testServer}:{self.remotePort}')
+ try:
+ server.starttls(context=context)
+ except smtplib.SMTPException as e:
+ if e.args[0] == 'STARTTLS extension not supported by server.':
+ unittest.skip(e.args[0])
+ else:
+ raise
+ server.ehlo()
+ server.quit()
+
+ def test_explicit_connect_starttls(self):
+ support.get_attribute(smtplib, 'SMTP_SSL')
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ context.check_hostname = False
+ context.verify_mode = ssl.CERT_NONE
+ with socket_helper.transient_internet(self.testServer):
+ server = smtplib.SMTP()
+ server.connect(self.testServer, self.remotePort)
+ try:
+ server.starttls(context=context)
+ except smtplib.SMTPException as e:
+ if e.args[0] == 'STARTTLS extension not supported by server.':
+ unittest.skip(e.args[0])
+ else:
+ raise
+ server.ehlo()
+ server.quit()
+
+ def test_explicit_connect_host_port_starttls(self):
+ support.get_attribute(smtplib, 'SMTP_SSL')
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ context.check_hostname = False
+ context.verify_mode = ssl.CERT_NONE
+ with socket_helper.transient_internet(self.testServer):
+ server = smtplib.SMTP()
+ server.connect(f'{self.testServer}:{self.remotePort}')
+ try:
+ server.starttls(context=context)
+ except smtplib.SMTPException as e:
+ if e.args[0] == 'STARTTLS extension not supported by server.':
+ unittest.skip(e.args[0])
+ else:
+ raise
+ server.ehlo()
+ server.quit()
+
class SmtpSSLTest(unittest.TestCase):
testServer = SMTP_TEST_SERVER
diff --git a/Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst b/Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst
new file mode 100644
index 000000000000000..8bb2cd188e89fa4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst
@@ -0,0 +1 @@
+Fixed bug where :meth:`smtplib.SMTP.starttls` could fail if :meth:`smtplib.SMTP.connect` is called explicitly rather than implicitly.
From d31a16e66224738d5bd0398385aa8b8a8cfe666a Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 9 Apr 2026 13:00:23 +0200
Subject: [PATCH 005/129] [3.14] gh-146646: Document that glob functions
suppress OSError (GH-147996) (#148288)
gh-146646: Document that glob functions suppress OSError (GH-147996)
(cherry picked from commit 8000a9de3c0b22f8202898a424c1008e13bd16ce)
Co-authored-by: WYSIATI
---
Doc/library/glob.rst | 10 ++++++++++
Doc/library/pathlib.rst | 10 ++++++++++
.../2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst | 3 +++
3 files changed, 23 insertions(+)
create mode 100644 Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst
diff --git a/Doc/library/glob.rst b/Doc/library/glob.rst
index 52c449281533372..b24e4da7bc8c079 100644
--- a/Doc/library/glob.rst
+++ b/Doc/library/glob.rst
@@ -83,6 +83,11 @@ The :mod:`!glob` module defines the following functions:
This function may return duplicate path names if *pathname*
contains multiple "``**``" patterns and *recursive* is true.
+ .. note::
+ Any :exc:`OSError` exceptions raised from scanning the filesystem are
+ suppressed. This includes :exc:`PermissionError` when accessing
+ directories without read permission.
+
.. versionchanged:: 3.5
Support for recursive globs using "``**``".
@@ -106,6 +111,11 @@ The :mod:`!glob` module defines the following functions:
This function may return duplicate path names if *pathname*
contains multiple "``**``" patterns and *recursive* is true.
+ .. note::
+ Any :exc:`OSError` exceptions raised from scanning the filesystem are
+ suppressed. This includes :exc:`PermissionError` when accessing
+ directories without read permission.
+
.. versionchanged:: 3.5
Support for recursive globs using "``**``".
diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst
index 9484103e5cece42..792cd255753c5bf 100644
--- a/Doc/library/pathlib.rst
+++ b/Doc/library/pathlib.rst
@@ -1365,6 +1365,11 @@ Reading directories
``False``, this method follows symlinks except when expanding "``**``"
wildcards. Set *recurse_symlinks* to ``True`` to always follow symlinks.
+ .. note::
+ Any :exc:`OSError` exceptions raised from scanning the filesystem are
+ suppressed. This includes :exc:`PermissionError` when accessing
+ directories without read permission.
+
.. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob
.. versionchanged:: 3.12
@@ -1391,6 +1396,11 @@ Reading directories
The paths are returned in no particular order.
If you need a specific order, sort the results.
+ .. note::
+ Any :exc:`OSError` exceptions raised from scanning the filesystem are
+ suppressed. This includes :exc:`PermissionError` when accessing
+ directories without read permission.
+
.. seealso::
:ref:`pathlib-pattern-language` and :meth:`Path.glob` documentation.
diff --git a/Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst b/Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst
new file mode 100644
index 000000000000000..4e89270442a33b1
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst
@@ -0,0 +1,3 @@
+Document that :func:`glob.glob`, :func:`glob.iglob`,
+:meth:`pathlib.Path.glob`, and :meth:`pathlib.Path.rglob` silently suppress
+:exc:`OSError` exceptions raised from scanning the filesystem.
From 88fc1e60038378fddb4db67a7dc7427fe9982a29 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 9 Apr 2026 13:59:06 +0200
Subject: [PATCH 006/129] [3.14] gh-148250: Mention str subclasses in
PyUnicodeWriter_WriteStr() doc (GH-148251) (#148293)
gh-148250: Mention str subclasses in PyUnicodeWriter_WriteStr() doc (GH-148251)
(cherry picked from commit 8c524503cd728d609d63d9024a9e2c418ba71f40)
Co-authored-by: Victor Stinner
---
Doc/c-api/unicode.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst
index 53a13e80944c703..ab58a64647ac610 100644
--- a/Doc/c-api/unicode.rst
+++ b/Doc/c-api/unicode.rst
@@ -1868,6 +1868,10 @@ object.
On success, return ``0``.
On error, set an exception, leave the writer unchanged, and return ``-1``.
+ To write a :class:`str` subclass which overrides the :meth:`~object.__str__`
+ method, :c:func:`PyUnicode_FromObject` can be used to get the original
+ string.
+
.. c:function:: int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj)
Call :c:func:`PyObject_Repr` on *obj* and write the output into *writer*.
From 571c337a5dd80e4dabc8fe0e92003d77b583665d Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 9 Apr 2026 15:39:39 +0200
Subject: [PATCH 007/129] [3.14] gh-106318: Add example for str.swapcase()
method (GH-144575) (#148296)
Co-authored-by: Adorilson Bezerra
---
Doc/library/stdtypes.rst | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index be1ef1a1d1c6caa..37b940a07e6cc0d 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -2771,8 +2771,22 @@ expression support in the :mod:`re` module).
.. method:: str.swapcase()
Return a copy of the string with uppercase characters converted to lowercase and
- vice versa. Note that it is not necessarily true that
- ``s.swapcase().swapcase() == s``.
+ vice versa. For example:
+
+ .. doctest::
+
+ >>> 'Hello World'.swapcase()
+ 'hELLO wORLD'
+
+ Note that it is not necessarily true that ``s.swapcase().swapcase() == s``.
+ For example:
+
+ .. doctest::
+
+ >>> 'straße'.swapcase().swapcase()
+ 'strasse'
+
+ See also :meth:`str.lower` and :meth:`str.upper`.
.. method:: str.title()
From bb78ec8fa85a6699db600bbcf30117686b67cea1 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 9 Apr 2026 15:49:09 +0200
Subject: [PATCH 008/129] [3.14] gh-148274: properly handle result from
`PyObject_VisitManagedDict` (GH-148275) (#148295)
gh-148274: properly handle result from `PyObject_VisitManagedDict` (GH-148275)
(cherry picked from commit ee2775cfae6bce18541e18797b67e09f2d12f72b)
Co-authored-by: Max Bachmann
---
Modules/_asynciomodule.c | 6 ++----
Modules/_testcapimodule.c | 3 +--
Objects/typevarobject.c | 9 +++------
3 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c
index 50ee180e4952352..75317b55ad6f70a 100644
--- a/Modules/_asynciomodule.c
+++ b/Modules/_asynciomodule.c
@@ -955,8 +955,7 @@ FutureObj_traverse(PyObject *op, visitproc visit, void *arg)
Py_VISIT(fut->fut_cancel_msg);
Py_VISIT(fut->fut_cancelled_exc);
Py_VISIT(fut->fut_awaited_by);
- PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
- return 0;
+ return PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
}
/*[clinic input]
@@ -2445,8 +2444,7 @@ TaskObj_traverse(PyObject *op, visitproc visit, void *arg)
Py_VISIT(fut->fut_cancel_msg);
Py_VISIT(fut->fut_cancelled_exc);
Py_VISIT(fut->fut_awaited_by);
- PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
- return 0;
+ return PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
}
/*[clinic input]
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 53f630832f8bbc4..567966ca558bd0f 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3203,9 +3203,8 @@ typedef struct {
} ManagedDictObject;
int ManagedDict_traverse(PyObject *self, visitproc visit, void *arg) {
- PyObject_VisitManagedDict(self, visit, arg);
Py_VISIT(Py_TYPE(self));
- return 0;
+ return PyObject_VisitManagedDict(self, visit, arg);
}
int ManagedDict_clear(PyObject *self) {
diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c
index c1737205c86b396..a52a4cb7d5b4d6f 100644
--- a/Objects/typevarobject.c
+++ b/Objects/typevarobject.c
@@ -498,8 +498,7 @@ typevar_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(tv->evaluate_constraints);
Py_VISIT(tv->default_value);
Py_VISIT(tv->evaluate_default);
- PyObject_VisitManagedDict(self, visit, arg);
- return 0;
+ return PyObject_VisitManagedDict(self, visit, arg);
}
static int
@@ -1193,8 +1192,7 @@ paramspec_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(ps->bound);
Py_VISIT(ps->default_value);
Py_VISIT(ps->evaluate_default);
- PyObject_VisitManagedDict(self, visit, arg);
- return 0;
+ return PyObject_VisitManagedDict(self, visit, arg);
}
static int
@@ -1690,8 +1688,7 @@ typevartuple_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(tvt->name);
Py_VISIT(tvt->default_value);
Py_VISIT(tvt->evaluate_default);
- PyObject_VisitManagedDict(self, visit, arg);
- return 0;
+ return PyObject_VisitManagedDict(self, visit, arg);
}
static int
From 1a3c03c3c98f91bec719398cdac6dd4847c44344 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 9 Apr 2026 16:07:55 +0200
Subject: [PATCH 009/129] [3.14] gh-148067: Fix typo in asyncio event loop
docs: 'signals' -> 'signal' (GH-148073) (#148246)
Co-authored-by: TT <70463940+Herrtian@users.noreply.github.com>
---
Doc/library/asyncio-eventloop.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
index d1a5b4e7b4638eb..79c9516cda2d60b 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -2055,7 +2055,7 @@ Wait until a file descriptor received some data using the
Set signal handlers for SIGINT and SIGTERM
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-(This ``signals`` example only works on Unix.)
+(This ``signal`` example only works on Unix.)
Register handlers for signals :const:`~signal.SIGINT` and :const:`~signal.SIGTERM`
using the :meth:`loop.add_signal_handler` method::
From 1f177749c52bfaa015e962cbf2e0e1c15d0979df Mon Sep 17 00:00:00 2001
From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Date: Thu, 9 Apr 2026 18:17:55 +0300
Subject: [PATCH 010/129] [3.14] gh-148254: Use singular "sec" in timeit
verbose output (GH-148290) (#148303)
Co-authored-by: gaweng <38250674+gaweng@users.noreply.github.com>
---
Lib/test/test_timeit.py | 28 +++++++++----------
Lib/timeit.py | 2 +-
...-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst | 2 ++
3 files changed, 17 insertions(+), 15 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst
diff --git a/Lib/test/test_timeit.py b/Lib/test/test_timeit.py
index 2aeebea9f93d436..2b0745e947cee9d 100644
--- a/Lib/test/test_timeit.py
+++ b/Lib/test/test_timeit.py
@@ -302,7 +302,7 @@ def test_main_help(self):
def test_main_verbose(self):
s = self.run_main(switches=['-v'])
self.assertEqual(s, dedent("""\
- 1 loop -> 1 secs
+ 1 loop -> 1 sec
raw times: 1 sec, 1 sec, 1 sec, 1 sec, 1 sec
@@ -312,19 +312,19 @@ def test_main_verbose(self):
def test_main_very_verbose(self):
s = self.run_main(seconds_per_increment=0.000_030, switches=['-vv'])
self.assertEqual(s, dedent("""\
- 1 loop -> 3e-05 secs
- 2 loops -> 6e-05 secs
- 5 loops -> 0.00015 secs
- 10 loops -> 0.0003 secs
- 20 loops -> 0.0006 secs
- 50 loops -> 0.0015 secs
- 100 loops -> 0.003 secs
- 200 loops -> 0.006 secs
- 500 loops -> 0.015 secs
- 1000 loops -> 0.03 secs
- 2000 loops -> 0.06 secs
- 5000 loops -> 0.15 secs
- 10000 loops -> 0.3 secs
+ 1 loop -> 3e-05 sec
+ 2 loops -> 6e-05 sec
+ 5 loops -> 0.00015 sec
+ 10 loops -> 0.0003 sec
+ 20 loops -> 0.0006 sec
+ 50 loops -> 0.0015 sec
+ 100 loops -> 0.003 sec
+ 200 loops -> 0.006 sec
+ 500 loops -> 0.015 sec
+ 1000 loops -> 0.03 sec
+ 2000 loops -> 0.06 sec
+ 5000 loops -> 0.15 sec
+ 10000 loops -> 0.3 sec
raw times: 300 msec, 300 msec, 300 msec, 300 msec, 300 msec
diff --git a/Lib/timeit.py b/Lib/timeit.py
index e767f0187826df6..0451f66bdf1ca78 100644
--- a/Lib/timeit.py
+++ b/Lib/timeit.py
@@ -319,7 +319,7 @@ def main(args=None, *, _wrap_timer=None):
callback = None
if verbose:
def callback(number, time_taken):
- msg = "{num} loop{s} -> {secs:.{prec}g} secs"
+ msg = "{num} loop{s} -> {secs:.{prec}g} sec"
plural = (number != 1)
print(msg.format(num=number, s='s' if plural else '',
secs=time_taken, prec=precision))
diff --git a/Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst b/Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst
new file mode 100644
index 000000000000000..818310c31b9de6f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst
@@ -0,0 +1,2 @@
+Use singular "sec" instead of "secs" in :mod:`timeit` verbose output for
+consistency with other time units.
From b87590fd275b992364b716ea79341fd6069009c5 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 9 Apr 2026 17:21:16 +0200
Subject: [PATCH 011/129] [3.14] gh-148091: clarify asyncio.Future.cancel(msg)
behaviour (GH-148248) (#148299)
gh-148091: clarify asyncio.Future.cancel(msg) behaviour (GH-148248)
(cherry picked from commit 2acb8d9257c4f5049777d9d462092373b0b3feca)
Co-authored-by: Manoj K M <136242596+manoj-k-m@users.noreply.github.com>
---
Doc/library/asyncio-future.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Doc/library/asyncio-future.rst b/Doc/library/asyncio-future.rst
index 4b69e569523c58b..43977de273e61f6 100644
--- a/Doc/library/asyncio-future.rst
+++ b/Doc/library/asyncio-future.rst
@@ -196,6 +196,10 @@ Future Object
Otherwise, change the Future's state to *cancelled*,
schedule the callbacks, and return ``True``.
+ The optional string argument *msg* is passed as the argument to the
+ :exc:`CancelledError` exception raised when a cancelled Future
+ is awaited.
+
.. versionchanged:: 3.9
Added the *msg* parameter.
From e8f3f7668f44ac83aa61f3ba0afe10e150b9fc21 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Fri, 10 Apr 2026 14:49:59 +0200
Subject: [PATCH 012/129] [3.14] gh-145831: email.quoprimime: `decode()` leaves
stray `\r` when `eol='\r\n'` (GH-145832) (#148312)
decoded[:-1] only strips one character, leaving a stray \r when eol
is two characters. Fix: decoded[:-len(eol)].
(cherry picked from commit 1a0edb1fa899c067f19b09598b45cdb6e733c4ee)
Co-authored-by: Stefan Zetzsche <120379523+stefanzetzsche@users.noreply.github.com>
---
Lib/email/quoprimime.py | 2 +-
Lib/test/test_email/test_email.py | 9 +++++++++
.../2026-03-11-15-09-52.gh-issue-145831._sW94w.rst | 2 ++
3 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst
diff --git a/Lib/email/quoprimime.py b/Lib/email/quoprimime.py
index 27c7ea55c7871fd..bc53b3768213102 100644
--- a/Lib/email/quoprimime.py
+++ b/Lib/email/quoprimime.py
@@ -272,7 +272,7 @@ def decode(encoded, eol=NL):
decoded += eol
# Special case if original string did not end with eol
if encoded[-1] not in '\r\n' and decoded.endswith(eol):
- decoded = decoded[:-1]
+ decoded = decoded[:-len(eol)]
return decoded
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index 0f138182fda2bab..3ba3e86ab65bd73 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -4827,6 +4827,15 @@ def test_decode_soft_line_break(self):
def test_decode_false_quoting(self):
self._test_decode('A=1,B=A ==> A+B==2', 'A=1,B=A ==> A+B==2')
+ def test_decode_crlf_eol_no_trailing_newline(self):
+ self._test_decode('abc', 'abc', eol='\r\n')
+
+ def test_decode_crlf_eol_multiline_no_trailing_newline(self):
+ self._test_decode('a\r\nb', 'a\r\nb', eol='\r\n')
+
+ def test_decode_crlf_eol_with_trailing_newline(self):
+ self._test_decode('abc\r\n', 'abc\r\n', eol='\r\n')
+
def _test_encode(self, body, expected_encoded_body, maxlinelen=None, eol=None):
kwargs = {}
if maxlinelen is None:
diff --git a/Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst b/Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst
new file mode 100644
index 000000000000000..454b62bc0db95fb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst
@@ -0,0 +1,2 @@
+Fix :func:`!email.quoprimime.decode` leaving a stray ``\r`` when
+``eol='\r\n'`` by stripping the full *eol* string instead of one character.
From 429c1d3c19a0d88fbe86e29b60227c77ef766915 Mon Sep 17 00:00:00 2001
From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Date: Fri, 10 Apr 2026 18:36:12 +0300
Subject: [PATCH 013/129] [3.14] Fix mixed line endings with pre-commit
(GH-148336) (#148338)
Co-authored-by: Zachary Ware
---
.pre-commit-config.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1d09596671ad19e..eaa3986bf6a0905 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -77,6 +77,9 @@ repos:
exclude: Lib/test/tokenizedata/coding20731.py
- id: end-of-file-fixer
files: '^\.github/CODEOWNERS$'
+ - id: mixed-line-ending
+ args: [--fix=auto]
+ exclude: '^Lib/test/.*data/'
- id: trailing-whitespace
types_or: [c, inc, python, rst, yaml]
- id: trailing-whitespace
From 288cbacfb91377eb591f44c9ae16e7cbf092abd8 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Fri, 10 Apr 2026 19:32:26 +0200
Subject: [PATCH 014/129] [3.14] gh-148284: Block inlining of gigantic
functions in ceval.c for clang 22 (GH-148334) (GH-148349)
gh-148284: Block inlining of gigantic functions in ceval.c for clang 22 (GH-148334)
(cherry picked from commit e007631e9949ab806742eb61076112e9e2e3e22e)
Co-authored-by: Ken Jin
Co-authored-by: Victor Stinner
---
Doc/using/configure.rst | 6 +++
Makefile.pre.in | 5 ++
...-04-10-14-20-54.gh-issue-148284.HKs-S_.rst | 1 +
configure | 47 +++++++++++++++++++
configure.ac | 28 +++++++++++
5 files changed, 87 insertions(+)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst
diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
index 157432acbc9d212..2c0d81d48007013 100644
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -1506,6 +1506,12 @@ Compiler flags
.. versionadded:: 3.7
+.. envvar:: CFLAGS_CEVAL
+
+ Flags used to compile ``Python/ceval.c``.
+
+ .. versionadded:: 3.14.5
+
.. envvar:: CCSHARED
Compiler flags used to build a shared library.
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 80a1b590c2f9b8c..797a6d6cceece00 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -126,6 +126,8 @@ PY_CORE_CFLAGS= $(PY_STDMODULE_CFLAGS) -DPy_BUILD_CORE
PY_CORE_LDFLAGS=$(PY_LDFLAGS) $(PY_LDFLAGS_NODIST)
# Strict or non-strict aliasing flags used to compile dtoa.c, see above
CFLAGS_ALIASING=@CFLAGS_ALIASING@
+# Compilation flags only for ceval.c.
+CFLAGS_CEVAL=@CFLAGS_CEVAL@
# Machine-dependent subdirectories
@@ -3142,6 +3144,9 @@ regen-jit:
Python/dtoa.o: Python/dtoa.c
$(CC) -c $(PY_CORE_CFLAGS) $(CFLAGS_ALIASING) -o $@ $<
+Python/ceval.o: Python/ceval.c
+ $(CC) -c $(PY_CORE_CFLAGS) $(CFLAGS_CEVAL) -o $@ $<
+
# Run reindent on the library
.PHONY: reindent
reindent:
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst
new file mode 100644
index 000000000000000..a74f6c1a61affd8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst
@@ -0,0 +1 @@
+Fix high stack consumption in Python's interpreter loop on Clang 22 by setting function limits for inlining when building with computed gotos.
diff --git a/configure b/configure
index 8cfdda5a29b00da..7621eeabeb85db9 100755
--- a/configure
+++ b/configure
@@ -826,6 +826,7 @@ OPENSSL_LDFLAGS
OPENSSL_LIBS
OPENSSL_INCLUDES
ENSUREPIP
+CFLAGS_CEVAL
SRCDIRS
THREADHEADERS
PANEL_LIBS
@@ -30049,6 +30050,52 @@ printf "%s\n" "#define HAVE_GLIBC_MEMMOVE_BUG 1" >>confdefs.h
fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we need to manually block large inlining in ceval.c" >&5
+printf %s "checking if we need to manually block large inlining in ceval.c... " >&6; }
+if test "$cross_compiling" = yes
+then :
+ block_huge_inlining_in_ceval=undefined
+else case e in #(
+ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int main(void) {
+// See gh-148284: Clang 22 seems to have interactions with inlining
+// and the stackref buffer which cause 40 kB of stack usage on x86-64
+// in buggy versions of _PyEval_EvalFrameDefault() in computed goto
+// interpreter. The normal usage seen is normally 1-2 kB.
+#if defined(__clang__) && (__clang_major__ == 22)
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"
+then :
+ block_huge_inlining_in_ceval=no
+else case e in #(
+ e) block_huge_inlining_in_ceval=yes ;;
+esac
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
+esac
+fi
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $block_huge_inlining_in_ceval" >&5
+printf "%s\n" "$block_huge_inlining_in_ceval" >&6; }
+
+if test "$block_huge_inlining_in_ceval" = yes && test "$ac_cv_computed_gotos" = yes; then
+ # gh-148284: Suppress inlining of functions whose stack size exceeds
+ # 512 bytes. This number should be tuned to follow the C stack
+ # consumption in _PyEval_EvalFrameDefault() on computed goto
+ # interpreter.
+ CFLAGS_CEVAL="$CFLAGS_CEVAL -finline-max-stacksize=512"
+fi
+
+
if test "$ac_cv_gcc_asm_for_x87" = yes; then
# Some versions of gcc miscompile inline asm:
# http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46491
diff --git a/configure.ac b/configure.ac
index 1acb91fd27b9d2b..21df2245402eea1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7244,6 +7244,34 @@ if test "$have_glibc_memmove_bug" = yes; then
for memmove and bcopy.])
fi
+AC_MSG_CHECKING([if we need to manually block large inlining in ceval.c])
+AC_RUN_IFELSE([AC_LANG_SOURCE([[
+int main(void) {
+// See gh-148284: Clang 22 seems to have interactions with inlining
+// and the stackref buffer which cause 40 kB of stack usage on x86-64
+// in buggy versions of _PyEval_EvalFrameDefault() in computed goto
+// interpreter. The normal usage seen is normally 1-2 kB.
+#if defined(__clang__) && (__clang_major__ == 22)
+ return 1;
+#else
+ return 0;
+#endif
+}
+]])],
+[block_huge_inlining_in_ceval=no],
+[block_huge_inlining_in_ceval=yes],
+[block_huge_inlining_in_ceval=undefined])
+AC_MSG_RESULT([$block_huge_inlining_in_ceval])
+
+if test "$block_huge_inlining_in_ceval" = yes && test "$ac_cv_computed_gotos" = yes; then
+ # gh-148284: Suppress inlining of functions whose stack size exceeds
+ # 512 bytes. This number should be tuned to follow the C stack
+ # consumption in _PyEval_EvalFrameDefault() on computed goto
+ # interpreter.
+ CFLAGS_CEVAL="$CFLAGS_CEVAL -finline-max-stacksize=512"
+fi
+AC_SUBST([CFLAGS_CEVAL])
+
if test "$ac_cv_gcc_asm_for_x87" = yes; then
# Some versions of gcc miscompile inline asm:
# http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46491
From f36da66c716e892aacdf9453325ff05b89fe7e70 Mon Sep 17 00:00:00 2001
From: Kumar Aditya
Date: Fri, 10 Apr 2026 23:59:38 +0530
Subject: [PATCH 015/129] =?UTF-8?q?[3.14]=20gh-148037:=20remove=20critical?=
=?UTF-8?q?=20section=20from=20`PyCode=5FAddr2Line`=20(GH=E2=80=A6=20(#148?=
=?UTF-8?q?353)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[3.14] gh-148037: remove critical section from `PyCode_Addr2Line` (GH-148103)
(cherry picked from commit d3b7b93cbbbf53061a95eb60cc116c9fec31c5b4)
---
Include/internal/pycore_instruments.h | 5 +--
...-04-09-14-18-33.gh-issue-148037.aP3CSX.rst | 1 +
Objects/codeobject.c | 24 ++++-------
Python/instrumentation.c | 41 +++++++++----------
Python/legacy_tracing.c | 4 +-
5 files changed, 33 insertions(+), 42 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst
diff --git a/Include/internal/pycore_instruments.h b/Include/internal/pycore_instruments.h
index 7658adca719e86d..98684ff1024f6e4 100644
--- a/Include/internal/pycore_instruments.h
+++ b/Include/internal/pycore_instruments.h
@@ -62,9 +62,6 @@ extern void
_Py_call_instrumentation_exc2(PyThreadState *tstate, int event,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, PyObject *arg0, PyObject *arg1);
-extern int
-_Py_Instrumentation_GetLine(PyCodeObject *code, int index);
-
extern PyObject _PyInstrumentation_MISSING;
extern PyObject _PyInstrumentation_DISABLE;
@@ -120,6 +117,8 @@ typedef struct _PyCoMonitoringData {
uint8_t *per_instruction_tools;
} _PyCoMonitoringData;
+extern int
+_Py_Instrumentation_GetLine(PyCodeObject *code, _PyCoLineInstrumentationData *line_data, int index);
#ifdef __cplusplus
}
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst
new file mode 100644
index 000000000000000..b0cef5951298172
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst
@@ -0,0 +1 @@
+Remove critical section from :c:func:`!PyCode_Addr2Line` in free-threading.
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index f0b2e5f36c60608..acaa9f5501af12f 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -1013,14 +1013,18 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
* source location tracking (co_lines/co_positions)
******************/
-static int
-_PyCode_Addr2Line(PyCodeObject *co, int addrq)
+int
+PyCode_Addr2Line(PyCodeObject *co, int addrq)
{
if (addrq < 0) {
return co->co_firstlineno;
}
- if (co->_co_monitoring && co->_co_monitoring->lines) {
- return _Py_Instrumentation_GetLine(co, addrq/sizeof(_Py_CODEUNIT));
+ _PyCoMonitoringData *data = _Py_atomic_load_ptr_acquire(&co->_co_monitoring);
+ if (data) {
+ _PyCoLineInstrumentationData *lines = _Py_atomic_load_ptr_acquire(&data->lines);
+ if (lines) {
+ return _Py_Instrumentation_GetLine(co, lines, addrq/sizeof(_Py_CODEUNIT));
+ }
}
assert(addrq >= 0 && addrq < _PyCode_NBYTES(co));
PyCodeAddressRange bounds;
@@ -1035,7 +1039,7 @@ _PyCode_SafeAddr2Line(PyCodeObject *co, int addrq)
return co->co_firstlineno;
}
if (co->_co_monitoring && co->_co_monitoring->lines) {
- return _Py_Instrumentation_GetLine(co, addrq/sizeof(_Py_CODEUNIT));
+ return _Py_Instrumentation_GetLine(co, co->_co_monitoring->lines, addrq/sizeof(_Py_CODEUNIT));
}
if (!(addrq >= 0 && addrq < _PyCode_NBYTES(co))) {
return -1;
@@ -1045,16 +1049,6 @@ _PyCode_SafeAddr2Line(PyCodeObject *co, int addrq)
return _PyCode_CheckLineNumber(addrq, &bounds);
}
-int
-PyCode_Addr2Line(PyCodeObject *co, int addrq)
-{
- int lineno;
- Py_BEGIN_CRITICAL_SECTION(co);
- lineno = _PyCode_Addr2Line(co, addrq);
- Py_END_CRITICAL_SECTION();
- return lineno;
-}
-
void
_PyLineTable_InitAddressRange(const char *linetable, Py_ssize_t length, int firstlineno, PyCodeAddressRange *range)
{
diff --git a/Python/instrumentation.c b/Python/instrumentation.c
index 32d6a2041828928..d06cda7c589c74b 100644
--- a/Python/instrumentation.c
+++ b/Python/instrumentation.c
@@ -1285,13 +1285,10 @@ _Py_call_instrumentation_exc2(
}
int
-_Py_Instrumentation_GetLine(PyCodeObject *code, int index)
+_Py_Instrumentation_GetLine(PyCodeObject *code, _PyCoLineInstrumentationData *line_data, int index)
{
- _PyCoMonitoringData *monitoring = code->_co_monitoring;
- assert(monitoring != NULL);
- assert(monitoring->lines != NULL);
+ assert(line_data != NULL);
assert(index < Py_SIZE(code));
- _PyCoLineInstrumentationData *line_data = monitoring->lines;
int line_delta = get_line_delta(line_data, index);
int line = compute_line(code, line_delta);
return line;
@@ -1309,11 +1306,11 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
_PyCoMonitoringData *monitoring = code->_co_monitoring;
_PyCoLineInstrumentationData *line_data = monitoring->lines;
PyInterpreterState *interp = tstate->interp;
- int line = _Py_Instrumentation_GetLine(code, i);
+ int line = _Py_Instrumentation_GetLine(code, line_data, i);
assert(line >= 0);
assert(prev != NULL);
int prev_index = (int)(prev - bytecode);
- int prev_line = _Py_Instrumentation_GetLine(code, prev_index);
+ int prev_line = _Py_Instrumentation_GetLine(code, line_data, prev_index);
if (prev_line == line) {
int prev_opcode = bytecode[prev_index].op.code;
/* RESUME and INSTRUMENTED_RESUME are needed for the operation of
@@ -1510,11 +1507,9 @@ initialize_tools(PyCodeObject *code)
}
static void
-initialize_lines(PyCodeObject *code, int bytes_per_entry)
+initialize_lines(_PyCoLineInstrumentationData *line_data, PyCodeObject *code, int bytes_per_entry)
{
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
- _PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines;
-
assert(line_data != NULL);
line_data->bytes_per_entry = bytes_per_entry;
int code_len = (int)Py_SIZE(code);
@@ -1655,18 +1650,19 @@ allocate_instrumentation_data(PyCodeObject *code)
ASSERT_WORLD_STOPPED_OR_LOCKED(code);
if (code->_co_monitoring == NULL) {
- code->_co_monitoring = PyMem_Malloc(sizeof(_PyCoMonitoringData));
- if (code->_co_monitoring == NULL) {
+ _PyCoMonitoringData *monitoring = PyMem_Malloc(sizeof(_PyCoMonitoringData));
+ if (monitoring == NULL) {
PyErr_NoMemory();
return -1;
}
- code->_co_monitoring->local_monitors = (_Py_LocalMonitors){ 0 };
- code->_co_monitoring->active_monitors = (_Py_LocalMonitors){ 0 };
- code->_co_monitoring->tools = NULL;
- code->_co_monitoring->lines = NULL;
- code->_co_monitoring->line_tools = NULL;
- code->_co_monitoring->per_instruction_opcodes = NULL;
- code->_co_monitoring->per_instruction_tools = NULL;
+ monitoring->local_monitors = (_Py_LocalMonitors){ 0 };
+ monitoring->active_monitors = (_Py_LocalMonitors){ 0 };
+ monitoring->tools = NULL;
+ monitoring->lines = NULL;
+ monitoring->line_tools = NULL;
+ monitoring->per_instruction_opcodes = NULL;
+ monitoring->per_instruction_tools = NULL;
+ _Py_atomic_store_ptr_release(&code->_co_monitoring, monitoring);
}
return 0;
}
@@ -1731,12 +1727,13 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
else {
bytes_per_entry = 5;
}
- code->_co_monitoring->lines = PyMem_Malloc(1 + code_len * bytes_per_entry);
- if (code->_co_monitoring->lines == NULL) {
+ _PyCoLineInstrumentationData *lines = PyMem_Malloc(1 + code_len * bytes_per_entry);
+ if (lines == NULL) {
PyErr_NoMemory();
return -1;
}
- initialize_lines(code, bytes_per_entry);
+ initialize_lines(lines, code, bytes_per_entry);
+ _Py_atomic_store_ptr_release(&code->_co_monitoring->lines, lines);
}
if (multitools && code->_co_monitoring->line_tools == NULL) {
code->_co_monitoring->line_tools = PyMem_Malloc(code_len);
diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c
index 9c72fab298201ef..2e29e1aa208ac0e 100644
--- a/Python/legacy_tracing.c
+++ b/Python/legacy_tracing.c
@@ -398,8 +398,8 @@ sys_trace_jump_func(
assert(PyCode_Check(code));
/* We can call _Py_Instrumentation_GetLine because we always set
* line events for tracing */
- int to_line = _Py_Instrumentation_GetLine(code, to);
- int from_line = _Py_Instrumentation_GetLine(code, from);
+ int to_line = _Py_Instrumentation_GetLine(code, code->_co_monitoring->lines, to);
+ int from_line = _Py_Instrumentation_GetLine(code, code->_co_monitoring->lines, from);
if (to_line != from_line) {
/* Will be handled by target INSTRUMENTED_LINE */
return &_PyInstrumentation_DISABLE;
From 620fb74384a1ddda9156c07d7e5e22453db48fda Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sat, 11 Apr 2026 17:30:24 +0200
Subject: [PATCH 016/129] [3.14] gh-148320: document that `import
sys.monitoring` raises `ModuleNotFoundError` (GH-148365) (#148385)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
gh-148320: document that `import sys.monitoring` raises `ModuleNotFoundError` (GH-148365)
(cherry picked from commit d7c9f1877cbd733d6c0fda395a9fcf78171f51b0)
Co-authored-by: Jonathan Dung
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
---
Doc/library/sys.monitoring.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst
index 4a460479e4afd74..16e6b1d6dc786b0 100644
--- a/Doc/library/sys.monitoring.rst
+++ b/Doc/library/sys.monitoring.rst
@@ -11,9 +11,9 @@
.. note::
:mod:`!sys.monitoring` is a namespace within the :mod:`sys` module,
- not an independent module, so there is no need to
- ``import sys.monitoring``, simply ``import sys`` and then use
- ``sys.monitoring``.
+ not an independent module, and ``import sys.monitoring`` would fail
+ with a :exc:`ModuleNotFoundError`. Instead, simply ``import sys``
+ and then use ``sys.monitoring``.
This namespace provides access to the functions and constants necessary to
From 6112e2dd444883ecfbc5e91a7b03ae07607a935a Mon Sep 17 00:00:00 2001
From: Kumar Aditya
Date: Sat, 11 Apr 2026 21:52:38 +0530
Subject: [PATCH 017/129] [3.14] gh-142518: add thread safety docs for dict and
set APIs (#148392)
Co-authored-by: Lysandros Nikolaou
Co-authored-by: Victor Stinner
---
Doc/c-api/dict.rst | 94 +++++++++++++++++++++++++++++++++++++++
Doc/c-api/set.rst | 31 +++++++++++++
Doc/data/threadsafety.dat | 88 +++++++++++++++++++++++++++++++++++-
3 files changed, 211 insertions(+), 2 deletions(-)
diff --git a/Doc/c-api/dict.rst b/Doc/c-api/dict.rst
index 9c4428ced41b5a2..1629e544d55fc27 100644
--- a/Doc/c-api/dict.rst
+++ b/Doc/c-api/dict.rst
@@ -65,6 +65,11 @@ Dictionary Objects
*key*, return ``1``, otherwise return ``0``. On error, return ``-1``.
This is equivalent to the Python expression ``key in p``.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
.. c:function:: int PyDict_ContainsString(PyObject *p, const char *key)
@@ -87,6 +92,11 @@ Dictionary Objects
``0`` on success or ``-1`` on failure. This function *does not* steal a
reference to *val*.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
.. c:function:: int PyDict_SetItemString(PyObject *p, const char *key, PyObject *val)
@@ -102,6 +112,11 @@ Dictionary Objects
If *key* is not in the dictionary, :exc:`KeyError` is raised.
Return ``0`` on success or ``-1`` on failure.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
.. c:function:: int PyDict_DelItemString(PyObject *p, const char *key)
@@ -120,6 +135,11 @@ Dictionary Objects
* If the key is missing, set *\*result* to ``NULL`` and return ``0``.
* On error, raise an exception and return ``-1``.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
.. versionadded:: 3.13
See also the :c:func:`PyObject_GetItem` function.
@@ -137,6 +157,13 @@ Dictionary Objects
:meth:`~object.__eq__` methods are silently ignored.
Prefer the :c:func:`PyDict_GetItemWithError` function instead.
+ .. note::
+
+ In the :term:`free-threaded build`, the returned
+ :term:`borrowed reference` may become invalid if another thread modifies
+ the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which
+ returns a :term:`strong reference`.
+
.. versionchanged:: 3.10
Calling this API without an :term:`attached thread state` had been allowed for historical
reason. It is no longer allowed.
@@ -149,6 +176,13 @@ Dictionary Objects
occurred. Return ``NULL`` **without** an exception set if the key
wasn't present.
+ .. note::
+
+ In the :term:`free-threaded build`, the returned
+ :term:`borrowed reference` may become invalid if another thread modifies
+ the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which
+ returns a :term:`strong reference`.
+
.. c:function:: PyObject* PyDict_GetItemString(PyObject *p, const char *key)
@@ -164,6 +198,13 @@ Dictionary Objects
Prefer using the :c:func:`PyDict_GetItemWithError` function with your own
:c:func:`PyUnicode_FromString` *key* instead.
+ .. note::
+
+ In the :term:`free-threaded build`, the returned
+ :term:`borrowed reference` may become invalid if another thread modifies
+ the dictionary concurrently. Prefer :c:func:`PyDict_GetItemStringRef`,
+ which returns a :term:`strong reference`.
+
.. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result)
@@ -184,6 +225,14 @@ Dictionary Objects
.. versionadded:: 3.4
+ .. note::
+
+ In the :term:`free-threaded build`, the returned
+ :term:`borrowed reference` may become invalid if another thread modifies
+ the dictionary concurrently. Prefer :c:func:`PyDict_SetDefaultRef`,
+ which returns a :term:`strong reference`.
+
+
.. c:function:: int PyDict_SetDefaultRef(PyObject *p, PyObject *key, PyObject *default_value, PyObject **result)
@@ -203,6 +252,11 @@ Dictionary Objects
These may refer to the same object: in that case you hold two separate
references to it.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
.. versionadded:: 3.13
@@ -220,6 +274,11 @@ Dictionary Objects
Similar to :meth:`dict.pop`, but without the default value and
not raising :exc:`KeyError` if the key is missing.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
.. versionadded:: 3.13
@@ -336,6 +395,13 @@ Dictionary Objects
only be added if there is not a matching key in *a*. Return ``0`` on
success or ``-1`` if an exception was raised.
+ .. note::
+
+ In the :term:`free-threaded build`, when *b* is a
+ :class:`dict` (with the standard iterator), both *a* and *b* are locked
+ for the duration of the operation. When *b* is a non-dict mapping, only
+ *a* is locked; *b* may be concurrently modified by another thread.
+
.. c:function:: int PyDict_Update(PyObject *a, PyObject *b)
@@ -345,6 +411,13 @@ Dictionary Objects
argument has no "keys" attribute. Return ``0`` on success or ``-1`` if an
exception was raised.
+ .. note::
+
+ In the :term:`free-threaded build`, when *b* is a
+ :class:`dict` (with the standard iterator), both *a* and *b* are locked
+ for the duration of the operation. When *b* is a non-dict mapping, only
+ *a* is locked; *b* may be concurrently modified by another thread.
+
.. c:function:: int PyDict_MergeFromSeq2(PyObject *a, PyObject *seq2, int override)
@@ -360,6 +433,13 @@ Dictionary Objects
if override or key not in a:
a[key] = value
+ .. note::
+
+ In the :term:`free-threaded ` build, only *a* is locked.
+ The iteration over *seq2* is not synchronized; *seq2* may be concurrently
+ modified by another thread.
+
+
.. c:function:: int PyDict_AddWatcher(PyDict_WatchCallback callback)
Register *callback* as a dictionary watcher. Return a non-negative integer
@@ -367,6 +447,13 @@ Dictionary Objects
of error (e.g. no more watcher IDs available), return ``-1`` and set an
exception.
+ .. note::
+
+ This function is not internally synchronized. In the
+ :term:`free-threaded ` build, callers should ensure no
+ concurrent calls to :c:func:`PyDict_AddWatcher` or
+ :c:func:`PyDict_ClearWatcher` are in progress.
+
.. versionadded:: 3.12
.. c:function:: int PyDict_ClearWatcher(int watcher_id)
@@ -375,6 +462,13 @@ Dictionary Objects
:c:func:`PyDict_AddWatcher`. Return ``0`` on success, ``-1`` on error (e.g.
if the given *watcher_id* was never registered.)
+ .. note::
+
+ This function is not internally synchronized. In the
+ :term:`free-threaded ` build, callers should ensure no
+ concurrent calls to :c:func:`PyDict_AddWatcher` or
+ :c:func:`PyDict_ClearWatcher` are in progress.
+
.. versionadded:: 3.12
.. c:function:: int PyDict_Watch(int watcher_id, PyObject *dict)
diff --git a/Doc/c-api/set.rst b/Doc/c-api/set.rst
index b74859dd669c54a..c81087697b498db 100644
--- a/Doc/c-api/set.rst
+++ b/Doc/c-api/set.rst
@@ -92,6 +92,11 @@ the constructor functions work with any iterable Python object.
actually iterable. The constructor is also useful for copying a set
(``c=set(s)``).
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *iterable* is a :class:`set`, :class:`frozenset` or :class:`dict`.
+
.. c:function:: PyObject* PyFrozenSet_New(PyObject *iterable)
@@ -100,6 +105,11 @@ the constructor functions work with any iterable Python object.
set on success or ``NULL`` on failure. Raise :exc:`TypeError` if *iterable* is
not actually iterable.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *iterable* is a :class:`set`, :class:`frozenset` or :class:`dict`.
+
The following functions and macros are available for instances of :class:`set`
or :class:`frozenset` or instances of their subtypes.
@@ -127,6 +137,10 @@ or :class:`frozenset` or instances of their subtypes.
the *key* is unhashable. Raise :exc:`SystemError` if *anyset* is not a
:class:`set`, :class:`frozenset`, or an instance of a subtype.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
.. c:function:: int PySet_Add(PyObject *set, PyObject *key)
@@ -138,6 +152,12 @@ or :class:`frozenset` or instances of their subtypes.
:exc:`SystemError` if *set* is not an instance of :class:`set` or its
subtype.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
+
The following functions are available for instances of :class:`set` or its
subtypes but not for instances of :class:`frozenset` or its subtypes.
@@ -152,6 +172,11 @@ subtypes but not for instances of :class:`frozenset` or its subtypes.
temporary frozensets. Raise :exc:`SystemError` if *set* is not an
instance of :class:`set` or its subtype.
+ .. note::
+
+ The operation is atomic on :term:`free threading `
+ when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`.
+
.. c:function:: PyObject* PySet_Pop(PyObject *set)
@@ -167,6 +192,12 @@ subtypes but not for instances of :class:`frozenset` or its subtypes.
success. Return ``-1`` and raise :exc:`SystemError` if *set* is not an instance of
:class:`set` or its subtype.
+ .. note::
+
+ In the :term:`free-threaded build`, the set is emptied before its entries
+ are cleared, so other threads will observe an empty set rather than
+ intermediate states.
+
Deprecated API
^^^^^^^^^^^^^^
diff --git a/Doc/data/threadsafety.dat b/Doc/data/threadsafety.dat
index 82edd1167ef1282..7f9110620db4f37 100644
--- a/Doc/data/threadsafety.dat
+++ b/Doc/data/threadsafety.dat
@@ -14,10 +14,71 @@
# The function name must match the C domain identifier used in the documentation.
# Synchronization primitives (Doc/c-api/synchronization.rst)
-PyMutex_Lock:shared:
-PyMutex_Unlock:shared:
+PyMutex_Lock:atomic:
+PyMutex_Unlock:atomic:
PyMutex_IsLocked:atomic:
+
+# Dictionary objects (Doc/c-api/dict.rst)
+
+# Type checks - read ob_type pointer, always safe
+PyDict_Check:atomic:
+PyDict_CheckExact:atomic:
+
+# Creation - pure allocation, no shared state
+PyDict_New:atomic:
+
+# Lock-free lookups - use _Py_dict_lookup_threadsafe(), no locking.
+# Atomic with simple types.
+PyDict_Contains:shared:
+PyDict_ContainsString:atomic:
+PyDict_GetItemRef:shared:
+PyDict_GetItemStringRef:atomic:
+PyDict_Size:atomic:
+PyDict_GET_SIZE:atomic:
+
+# Borrowed-reference lookups - lock-free dict access but returned
+# borrowed reference is unsafe in free-threaded builds without
+# external synchronization
+PyDict_GetItem:compatible:
+PyDict_GetItemWithError:compatible:
+PyDict_GetItemString:compatible:
+PyDict_SetDefault:compatible:
+
+# Iteration - no locking; returns borrowed refs
+PyDict_Next:compatible:
+
+# Single-item mutations - protected by per-object critical section
+PyDict_SetItem:shared:
+PyDict_SetItemString:atomic:
+PyDict_DelItem:shared:
+PyDict_DelItemString:atomic:
+PyDict_SetDefaultRef:shared:
+PyDict_Pop:shared:
+PyDict_PopString:atomic:
+
+# Bulk reads - hold per-object lock for duration
+PyDict_Clear:atomic:
+PyDict_Copy:atomic:
+PyDict_Keys:atomic:
+PyDict_Values:atomic:
+PyDict_Items:atomic:
+
+# Merge/update - lock target dict; also lock source when it is a dict
+PyDict_Update:shared:
+PyDict_Merge:shared:
+PyDict_MergeFromSeq2:shared:
+
+# Watcher registration - no synchronization on interpreter state
+PyDict_AddWatcher:compatible:
+PyDict_ClearWatcher:compatible:
+
+# Per-dict watcher tags - non-atomic RMW on _ma_watcher_tag;
+# safe on distinct dicts only
+PyDict_Watch:distinct:
+PyDict_Unwatch:distinct:
+
+
# List objects (Doc/c-api/list.rst)
# Type checks - read ob_type pointer, always safe
@@ -125,6 +186,29 @@ PyByteArray_GET_SIZE:atomic:
PyByteArray_AsString:compatible:
PyByteArray_AS_STRING:compatible:
+# Creation - may iterate the iterable argument, calling arbitrary code.
+# Atomic for sets, frozensets, dicts, and frozendicts.
+PySet_New:shared:
+PyFrozenSet_New:shared:
+
+# Size - uses atomic load on free-threaded builds
+PySet_Size:atomic:
+PySet_GET_SIZE:atomic:
+
+# Contains - lock-free, atomic with simple types
+PySet_Contains:shared:
+
+# Mutations - hold per-object lock for duration
+# atomic with simple types
+PySet_Add:shared:
+PySet_Discard:shared:
+
+# Pop - hold per-object lock for duration
+PySet_Pop:atomic:
+
+# Clear - empties the set before clearing
+PySet_Clear:atomic:
+
# Capsule objects (Doc/c-api/capsule.rst)
# Type check - read ob_type pointer, always safe
From d6be9fb07731efaaf70027b38cfc752caa0b91d2 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sat, 11 Apr 2026 23:54:29 +0200
Subject: [PATCH 018/129] [3.14] gh-146264: Use static HACL deps for static
module builds (GH-146265) (#148403)
gh-146264: Use static HACL deps for static module builds (GH-146265)
(cherry picked from commit f445d2e8666c5585d613add075cabe2abc7f972b)
Co-authored-by: Ihar Hrachyshka
---
.../next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst | 3 +++
configure | 2 +-
configure.ac | 2 +-
3 files changed, 5 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst
diff --git a/Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst b/Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst
new file mode 100644
index 000000000000000..1fdafe560432a66
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst
@@ -0,0 +1,3 @@
+Fix static module builds on non-WASI targets by linking HACL dependencies as
+static libraries when ``MODULE_BUILDTYPE=static``, preventing duplicate
+``_Py_LibHacl_*`` symbol errors at link time.
diff --git a/configure b/configure
index 7621eeabeb85db9..1cc1d2a2a54dd48 100755
--- a/configure
+++ b/configure
@@ -32758,7 +32758,7 @@ fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for HACL* library linking type" >&5
printf %s "checking for HACL* library linking type... " >&6; }
-if test "$ac_sys_system" = "WASI"; then
+if test "$ac_sys_system" = "WASI" || test "$MODULE_BUILDTYPE" = "static"; then
LIBHACL_LDEPS_LIBTYPE=STATIC
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: static" >&5
printf "%s\n" "static" >&6; }
diff --git a/configure.ac b/configure.ac
index 21df2245402eea1..21cef7a98181b66 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8089,7 +8089,7 @@ AC_SUBST([LIBHACL_BLAKE2_SIMD256_OBJS])
# HACL*-based cryptographic primitives
AC_MSG_CHECKING([for HACL* library linking type])
-if test "$ac_sys_system" = "WASI"; then
+if test "$ac_sys_system" = "WASI" || test "$MODULE_BUILDTYPE" = "static"; then
LIBHACL_LDEPS_LIBTYPE=STATIC
AC_MSG_RESULT([static])
else
From 4f8a77bf3fa8d0c10ddd50a8dda4ad89e8d2434e Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sun, 12 Apr 2026 00:46:26 +0200
Subject: [PATCH 019/129] [3.14] gh-145105: Fix crash in csv.reader with
re-entrant iterator (GH-145106) (#148404)
gh-145105: Fix crash in csv.reader with re-entrant iterator (GH-145106)
When a custom iterator calls next() on the same csv.reader from
within __next__, the inner iteration sets self->fields to NULL.
The outer iteration then crashes in parse_save_field() by passing
NULL to PyList_Append.
Add a guard after PyIter_Next() to detect that fields was set to
NULL by a re-entrant call, and raise csv.Error instead of crashing.
(cherry picked from commit 20994b1809f9c162e4cae01a5af08bd492ede9f9)
Co-authored-by: Ramin Farajpour Cami
---
Lib/test/test_csv.py | 27 +++++++++++++++++++
...0.gh-issue-145105.csv-reader-reentrant.rst | 2 ++
Modules/_csv.c | 6 +++++
3 files changed, 35 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index 98ee0c3cdd7a06f..a0a1e91777d8934 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -553,6 +553,33 @@ def test_roundtrip_escaped_unquoted_newlines(self):
self.assertEqual(row, rows[i])
+ def test_reader_reentrant_iterator(self):
+ # gh-145105: re-entering the reader from the iterator must not crash.
+ class ReentrantIter:
+ def __init__(self):
+ self.reader = None
+ self.n = 0
+ def __iter__(self):
+ return self
+ def __next__(self):
+ self.n += 1
+ if self.n == 1:
+ try:
+ next(self.reader)
+ except StopIteration:
+ pass
+ return "a,b"
+ if self.n == 2:
+ return "x"
+ raise StopIteration
+
+ it = ReentrantIter()
+ reader = csv.reader(it)
+ it.reader = reader
+ with self.assertRaises(csv.Error):
+ next(reader)
+
+
class TestDialectRegistry(unittest.TestCase):
def test_registry_badargs(self):
self.assertRaises(TypeError, csv.list_dialects, None)
diff --git a/Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst b/Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst
new file mode 100644
index 000000000000000..bc61cc43a5aa339
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst
@@ -0,0 +1,2 @@
+Fix crash in :mod:`csv` reader when iterating with a re-entrant iterator
+that calls :func:`next` on the same reader from within ``__next__``.
diff --git a/Modules/_csv.c b/Modules/_csv.c
index b994a42775178da..fd681f81a3b1972 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -965,6 +965,12 @@ Reader_iternext_lock_held(PyObject *op)
Py_DECREF(lineobj);
return NULL;
}
+ if (self->fields == NULL) {
+ PyErr_SetString(module_state->error_obj,
+ "iterator has already advanced the reader");
+ Py_DECREF(lineobj);
+ return NULL;
+ }
++self->line_num;
kind = PyUnicode_KIND(lineobj);
data = PyUnicode_DATA(lineobj);
From af2f5189a12888afa4e93243653c331e9fffddb5 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sun, 12 Apr 2026 02:05:10 +0200
Subject: [PATCH 020/129] [3.14] gh-146333: Fix quadratic regex backtracking in
configparser option parsing (GH-146399) (#148287)
gh-146333: Fix quadratic regex backtracking in configparser option parsing (GH-146399)
Use negative lookahead in option regex to prevent backtracking, and to avoid changing logic outside the regexes (since people could use the regex directly).
(cherry picked from commit 7e0a0be4097f9d29d66fe23f5af86f18a34ed7dd)
Co-authored-by: Joshua Swanson <22283299+joshuaswanson@users.noreply.github.com>
---
Lib/configparser.py | 8 ++++++--
Lib/test/test_configparser.py | 20 +++++++++++++++++++
...3-25-00-51-03.gh-issue-146333.LqdL__bn.rst | 3 +++
3 files changed, 29 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-03-25-00-51-03.gh-issue-146333.LqdL__bn.rst
diff --git a/Lib/configparser.py b/Lib/configparser.py
index d435a5c2fe0da24..e76647d339e9138 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -613,7 +613,9 @@ class RawConfigParser(MutableMapping):
\] # ]
"""
_OPT_TMPL = r"""
- (?P
From c171c52c3c940ddc8ec674b24a972af576b6de06 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Mon, 20 Apr 2026 16:43:12 +0200
Subject: [PATCH 058/129] [3.14] Docs: Fix some typos in `calendar.rst`
(GH-148756) (GH-148795)
Docs: Fix some typos in `calendar.rst` (GH-148756)
(cherry picked from commit 983c7462d65abc82d80345aa4769c1907522f310)
Co-authored-by: Manoj K M
---
Doc/library/calendar.rst | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst
index 73b3b8b4da4ca95..1a4323134b2ee99 100644
--- a/Doc/library/calendar.rst
+++ b/Doc/library/calendar.rst
@@ -56,13 +56,13 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
.. method:: setfirstweekday(firstweekday)
- Set the first weekday to *firstweekday*, passed as an :class:`int` (0--6)
+ Set the first weekday to *firstweekday*, passed as an :class:`int` (0--6).
Identical to setting the :attr:`~Calendar.firstweekday` property.
.. method:: iterweekdays()
- Return an iterator for the week day numbers that will be used for one
+ Return an iterator for the weekday numbers that will be used for one
week. The first value from the iterator will be the same as the value of
the :attr:`~Calendar.firstweekday` property.
@@ -88,7 +88,7 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
Return an iterator for the month *month* in the year *year* similar to
:meth:`itermonthdates`, but not restricted by the :class:`datetime.date`
range. Days returned will be tuples consisting of a day of the month
- number and a week day number.
+ number and a weekday number.
.. method:: itermonthdays3(year, month)
@@ -405,7 +405,7 @@ For simple text calendars this module provides the following functions.
.. function:: monthrange(year, month)
- Returns weekday of first day of the month and number of days in month, for the
+ Returns weekday of first day of the month and number of days in month, for the
specified *year* and *month*.
@@ -443,7 +443,7 @@ For simple text calendars this module provides the following functions.
An unrelated but handy function that takes a time tuple such as returned by
the :func:`~time.gmtime` function in the :mod:`time` module, and returns the
corresponding Unix timestamp value, assuming an epoch of 1970, and the POSIX
- encoding. In fact, :func:`time.gmtime` and :func:`timegm` are each others'
+ encoding. In fact, :func:`time.gmtime` and :func:`timegm` are each other's
inverse.
From c235654cba26a4f538256842cec8ff1fa9f22331 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Tue, 21 Apr 2026 01:10:52 +0200
Subject: [PATCH 059/129] [3.14] gh-148814: Fix an issue in Emscripten README
(GH-148752) (#148815)
Correct the description of the default state of test module compilation.
(cherry picked from commit d206d42834b2a34aee11b048357131371cf6947d)
Co-authored-by: Stan Ulbrych
---
Platforms/emscripten/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Platforms/emscripten/README.md b/Platforms/emscripten/README.md
index c1fb1dd53567719..82d338c7781c820 100644
--- a/Platforms/emscripten/README.md
+++ b/Platforms/emscripten/README.md
@@ -243,8 +243,8 @@ await createEmscriptenModule({
are not shipped. All other modules are bundled as pre-compiled
``pyc`` files.
- In-memory file system (MEMFS) is not persistent and limited.
-- Test modules are disabled by default. Use ``--enable-test-modules`` build
- test modules like ``_testcapi``.
+- Test modules are built by default. Use ``--disable-test-modules`` to disable
+ building test modules like ``_testcapi``.
## WASI (wasm32-wasi)
From b1cf9016335cb637c5a425032e8274a224f4b2ed Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Tue, 21 Apr 2026 09:49:37 +0200
Subject: [PATCH 060/129] [3.14] gh-146211: Reject CR/LF in HTTP tunnel request
headers (GH-146212) (#148342)
gh-146211: Reject CR/LF in HTTP tunnel request headers (GH-146212)
(cherry picked from commit 05ed7ce7ae9e17c23a04085b2539fe6d6d3cef69)
Co-authored-by: Seth Larson
Co-authored-by: Illia Volochii
---
Lib/http/client.py | 11 ++++-
Lib/test/test_httplib.py | 45 +++++++++++++++++++
...-03-20-09-29-42.gh-issue-146211.PQVbs7.rst | 2 +
3 files changed, 57 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst
diff --git a/Lib/http/client.py b/Lib/http/client.py
index 77f8d26291dfc23..6fb7d254ea9c273 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -972,13 +972,22 @@ def _wrap_ipv6(self, ip):
return ip
def _tunnel(self):
+ if _contains_disallowed_url_pchar_re.search(self._tunnel_host):
+ raise ValueError('Tunnel host can\'t contain control characters %r'
+ % (self._tunnel_host,))
connect = b"CONNECT %s:%d %s\r\n" % (
self._wrap_ipv6(self._tunnel_host.encode("idna")),
self._tunnel_port,
self._http_vsn_str.encode("ascii"))
headers = [connect]
for header, value in self._tunnel_headers.items():
- headers.append(f"{header}: {value}\r\n".encode("latin-1"))
+ header_bytes = header.encode("latin-1")
+ value_bytes = value.encode("latin-1")
+ if not _is_legal_header_name(header_bytes):
+ raise ValueError('Invalid header name %r' % (header_bytes,))
+ if _is_illegal_header_value(value_bytes):
+ raise ValueError('Invalid header value %r' % (value_bytes,))
+ headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes))
headers.append(b"\r\n")
# Making a single send() call instead of one per line encourages
# the host OS to use a more optimal packet size instead of
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
index bcb828edec7c39b..6f3eac6b98a4de9 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -369,6 +369,51 @@ def test_invalid_headers(self):
with self.assertRaisesRegex(ValueError, 'Invalid header'):
conn.putheader(name, value)
+ def test_invalid_tunnel_headers(self):
+ cases = (
+ ('Invalid\r\nName', 'ValidValue'),
+ ('Invalid\rName', 'ValidValue'),
+ ('Invalid\nName', 'ValidValue'),
+ ('\r\nInvalidName', 'ValidValue'),
+ ('\rInvalidName', 'ValidValue'),
+ ('\nInvalidName', 'ValidValue'),
+ (' InvalidName', 'ValidValue'),
+ ('\tInvalidName', 'ValidValue'),
+ ('Invalid:Name', 'ValidValue'),
+ (':InvalidName', 'ValidValue'),
+ ('ValidName', 'Invalid\r\nValue'),
+ ('ValidName', 'Invalid\rValue'),
+ ('ValidName', 'Invalid\nValue'),
+ ('ValidName', 'InvalidValue\r\n'),
+ ('ValidName', 'InvalidValue\r'),
+ ('ValidName', 'InvalidValue\n'),
+ )
+ for name, value in cases:
+ with self.subTest((name, value)):
+ conn = client.HTTPConnection('example.com')
+ conn.set_tunnel('tunnel', headers={
+ name: value
+ })
+ conn.sock = FakeSocket('')
+ with self.assertRaisesRegex(ValueError, 'Invalid header'):
+ conn._tunnel() # Called in .connect()
+
+ def test_invalid_tunnel_host(self):
+ cases = (
+ 'invalid\r.host',
+ '\ninvalid.host',
+ 'invalid.host\r\n',
+ 'invalid.host\x00',
+ 'invalid host',
+ )
+ for tunnel_host in cases:
+ with self.subTest(tunnel_host):
+ conn = client.HTTPConnection('example.com')
+ conn.set_tunnel(tunnel_host)
+ conn.sock = FakeSocket('')
+ with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'):
+ conn._tunnel() # Called in .connect()
+
def test_headers_debuglevel(self):
body = (
b'HTTP/1.1 200 OK\r\n'
diff --git a/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst
new file mode 100644
index 000000000000000..4993633b8ebebb2
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst
@@ -0,0 +1,2 @@
+Reject CR/LF characters in tunnel request headers for the
+HTTPConnection.set_tunnel() method.
From 27522b7d6e6588f03e61099dd858cd5a9314e2f2 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Tue, 21 Apr 2026 18:26:19 +0200
Subject: [PATCH 061/129] =?UTF-8?q?[3.14]=20gh-148808:=20Add=20boundary=20?=
=?UTF-8?q?check=20to=20asyncio.AbstractEventLoop.sock=5Frecvf=E2=80=A6=20?=
=?UTF-8?q?(GH-148809)=20(#148837)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
gh-148808: Add boundary check to asyncio.AbstractEventLoop.sock_recvf… (GH-148809)
(cherry picked from commit 1274766d3c29007ab77245a72abbf8dce2a9db4d)
Co-authored-by: Seth Larson
---
Lib/test/test_asyncio/test_sock_lowlevel.py | 21 +++++++++++++++++++
...-04-20-15-31-37.gh-issue-148808._Z8JL0.rst | 3 +++
Modules/overlapped.c | 5 +++++
3 files changed, 29 insertions(+)
create mode 100644 Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst
diff --git a/Lib/test/test_asyncio/test_sock_lowlevel.py b/Lib/test/test_asyncio/test_sock_lowlevel.py
index df4ec7948975f60..f32dcd589e2de22 100644
--- a/Lib/test/test_asyncio/test_sock_lowlevel.py
+++ b/Lib/test/test_asyncio/test_sock_lowlevel.py
@@ -427,6 +427,27 @@ def test_recvfrom_into(self):
self.loop.run_until_complete(
self._basetest_datagram_recvfrom_into(server_address))
+ async def _basetest_datagram_recvfrom_into_wrong_size(self, server_address):
+ # Call sock_sendto() with a size larger than the buffer
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
+ sock.setblocking(False)
+
+ buf = bytearray(5000)
+ data = b'\x01' * 4096
+ wrong_size = len(buf) + 1
+ await self.loop.sock_sendto(sock, data, server_address)
+ with self.assertRaises(ValueError):
+ await self.loop.sock_recvfrom_into(
+ sock, buf, wrong_size)
+
+ size, addr = await self.loop.sock_recvfrom_into(sock, buf)
+ self.assertEqual(buf[:size], data)
+
+ def test_recvfrom_into_wrong_size(self):
+ with test_utils.run_udp_echo_server() as server_address:
+ self.loop.run_until_complete(
+ self._basetest_datagram_recvfrom_into_wrong_size(server_address))
+
async def _basetest_datagram_sendto_blocking(self, server_address):
# Sad path, sock.sendto() raises BlockingIOError
# This involves patching sock.sendto() to raise BlockingIOError but
diff --git a/Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst b/Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst
new file mode 100644
index 000000000000000..0b5cf85fedfba10
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst
@@ -0,0 +1,3 @@
+Added buffer boundary check when using ``nbytes`` parameter with
+:meth:`!asyncio.AbstractEventLoop.sock_recvfrom_into`. Only
+relevant for Windows and the :class:`asyncio.ProactorEventLoop`.
diff --git a/Modules/overlapped.c b/Modules/overlapped.c
index 09b57ce4b9773ab..9a5687c05e19a54 100644
--- a/Modules/overlapped.c
+++ b/Modules/overlapped.c
@@ -1914,6 +1914,11 @@ _overlapped_Overlapped_WSARecvFromInto_impl(OverlappedObject *self,
}
#endif
+ if (bufobj->len < (Py_ssize_t)size) {
+ PyErr_SetString(PyExc_ValueError, "nbytes is greater than the length of the buffer");
+ return NULL;
+ }
+
wsabuf.buf = bufobj->buf;
wsabuf.len = size;
From 5aa8234cce8b6746006b7d38f10763abf1393574 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Tue, 21 Apr 2026 22:31:46 +0200
Subject: [PATCH 062/129] [3.14] gh-146578: _zstd: Fix printf format for
pledged size errors (GH-146576) (#148855)
gh-146578: _zstd: Fix printf format for pledged size errors (GH-146576)
Use %llu instead of %ull for unsigned long long in zstd_contentsize_converter ValueError messages.
(cherry picked from commit 09233bd19879284395aff97d7357b693893e6dd7)
Co-authored-by: cui
---
Modules/_zstd/compressor.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Modules/_zstd/compressor.c b/Modules/_zstd/compressor.c
index 508b136817872bb..45a1dd8da29073a 100644
--- a/Modules/_zstd/compressor.c
+++ b/Modules/_zstd/compressor.c
@@ -74,7 +74,7 @@ zstd_contentsize_converter(PyObject *size, unsigned long long *p)
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
PyErr_Format(PyExc_ValueError,
"size argument should be a positive int less "
- "than %ull", ZSTD_CONTENTSIZE_ERROR);
+ "than %llu", ZSTD_CONTENTSIZE_ERROR);
return 0;
}
return 0;
@@ -83,7 +83,7 @@ zstd_contentsize_converter(PyObject *size, unsigned long long *p)
*p = ZSTD_CONTENTSIZE_ERROR;
PyErr_Format(PyExc_ValueError,
"size argument should be a positive int less "
- "than %ull", ZSTD_CONTENTSIZE_ERROR);
+ "than %llu", ZSTD_CONTENTSIZE_ERROR);
return 0;
}
*p = pledged_size;
From e5d554168337fc53f4fd5ff0dbeb7c3460ce80db Mon Sep 17 00:00:00 2001
From: Sam Gross
Date: Wed, 22 Apr 2026 14:59:58 -0400
Subject: [PATCH 063/129] [3.14] gh-148820: Fix _PyRawMutex use-after-free on
spurious semaphore wakeup (gh-148852) (#148884)
_PyRawMutex_UnlockSlow CAS-removes the waiter from the list and then
calls _PySemaphore_Wakeup, with no handshake. If _PySemaphore_Wait
returns Py_PARK_INTR, the waiter can destroy its stack-allocated
semaphore before the unlocker's Wakeup runs, causing a fatal error from
ReleaseSemaphore / sem_post.
Loop in _PyRawMutex_LockSlow until _PySemaphore_Wait returns Py_PARK_OK,
which is only signalled when a matching Wakeup has been observed.
Also include GetLastError() and the handle in the Windows fatal messages
in _PySemaphore_Init, _PySemaphore_Wait, and _PySemaphore_Wakeup to make
similar races easier to diagnose in the future.
(cherry picked from commit ad3c5b7958b890382f431a53349320cb7c84d405)
---
.../2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst | 5 +++++
Python/lock.c | 11 ++++++++++-
Python/parking_lot.c | 12 ++++++++----
3 files changed, 23 insertions(+), 5 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst
new file mode 100644
index 000000000000000..392becaffb73cf7
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-14-36-44.gh-issue-148820.XhOGhA.rst
@@ -0,0 +1,5 @@
+Fix a race in :c:type:`!_PyRawMutex` on the free-threaded build where a
+``Py_PARK_INTR`` return from ``_PySemaphore_Wait`` could let the waiter
+destroy its semaphore before the unlocking thread's
+``_PySemaphore_Wakeup`` completed, causing a fatal ``ReleaseSemaphore``
+error.
diff --git a/Python/lock.c b/Python/lock.c
index ca269bd2cb4b518..62f2637bcfb4531 100644
--- a/Python/lock.c
+++ b/Python/lock.c
@@ -212,7 +212,16 @@ _PyRawMutex_LockSlow(_PyRawMutex *m)
// Wait for us to be woken up. Note that we still have to lock the
// mutex ourselves: it is NOT handed off to us.
- _PySemaphore_Wait(&waiter.sema, -1, /*detach=*/0);
+ //
+ // Loop until we observe an actual wakeup. A return of Py_PARK_INTR
+ // could otherwise let us exit _PySemaphore_Wait and destroy
+ // `waiter.sema` while _PyRawMutex_UnlockSlow's matching
+ // _PySemaphore_Wakeup is still pending, since the unlocker has
+ // already CAS-removed us from the waiter list without any handshake.
+ int res;
+ do {
+ res = _PySemaphore_Wait(&waiter.sema, -1, /*detach=*/0);
+ } while (res != Py_PARK_OK);
}
_PySemaphore_Destroy(&waiter.sema);
diff --git a/Python/parking_lot.c b/Python/parking_lot.c
index 0ffea97b079943a..01901f5c09be1b5 100644
--- a/Python/parking_lot.c
+++ b/Python/parking_lot.c
@@ -61,7 +61,9 @@ _PySemaphore_Init(_PySemaphore *sema)
NULL // unnamed
);
if (!sema->platform_sem) {
- Py_FatalError("parking_lot: CreateSemaphore failed");
+ _Py_FatalErrorFormat(__func__,
+ "parking_lot: CreateSemaphore failed (error: %u)",
+ GetLastError());
}
#elif defined(_Py_USE_SEMAPHORES)
if (sem_init(&sema->platform_sem, /*pshared=*/0, /*value=*/0) < 0) {
@@ -141,8 +143,8 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout)
}
else {
_Py_FatalErrorFormat(__func__,
- "unexpected error from semaphore: %u (error: %u)",
- wait, GetLastError());
+ "unexpected error from semaphore: %u (error: %u, handle: %p)",
+ wait, GetLastError(), sema->platform_sem);
}
#elif defined(_Py_USE_SEMAPHORES)
int err;
@@ -251,7 +253,9 @@ _PySemaphore_Wakeup(_PySemaphore *sema)
{
#if defined(MS_WINDOWS)
if (!ReleaseSemaphore(sema->platform_sem, 1, NULL)) {
- Py_FatalError("parking_lot: ReleaseSemaphore failed");
+ _Py_FatalErrorFormat(__func__,
+ "parking_lot: ReleaseSemaphore failed (error: %u, handle: %p)",
+ GetLastError(), sema->platform_sem);
}
#elif defined(_Py_USE_SEMAPHORES)
int err = sem_post(&sema->platform_sem);
From 5f1b710a2883bd5a4af25d055b60c512d568feea Mon Sep 17 00:00:00 2001
From: Stan Ulbrych
Date: Wed, 22 Apr 2026 22:19:25 +0100
Subject: [PATCH 064/129] [3.14] gh-148801: Fix unbound C recursion in
`Element.__deepcopy__()` (GH-148802) (#148842)
(cherry picked from commit 33e82be1746a964b595b2bba64f38a5787681eb3)
---
Lib/test/test_xml_etree.py | 13 +++++++++++
...-04-20-18-29-21.gh-issue-148801.ROeNqs.rst | 2 ++
Modules/_elementtree.c | 23 +++++++++++++------
3 files changed, 31 insertions(+), 7 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 0b343cc4bb83714..b207dbe68be94cf 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -3098,6 +3098,19 @@ def __deepcopy__(self, memo):
self.assertEqual([c.tag for c in children[3:]],
[a.tag, b.tag, a.tag, b.tag])
+ @support.skip_if_unlimited_stack_size
+ @support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
+ def test_deeply_nested_deepcopy(self):
+ # This should raise a RecursionError and not crash.
+ # See https://github.com/python/cpython/issues/148801.
+ root = cur = ET.Element('s')
+ for _ in range(150_000):
+ cur = ET.SubElement(cur, 'u')
+ with support.infinite_recursion():
+ with self.assertRaises(RecursionError):
+ copy.deepcopy(root)
+
class MutationDeleteElementPath(str):
def __new__(cls, elem, *args):
diff --git a/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst
new file mode 100644
index 000000000000000..6fcd30e8f057b95
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst
@@ -0,0 +1,2 @@
+:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.__deepcopy__
+` on deeply nested trees.
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 22d3205e6ad3148..496864175725e63 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -16,6 +16,7 @@
#endif
#include "Python.h"
+#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
#include "pycore_pyhash.h" // _Py_HashSecret
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
@@ -802,26 +803,31 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
/*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/
{
Py_ssize_t i;
- ElementObject* element;
+ ElementObject* element = NULL;
PyObject* tag;
PyObject* attrib;
PyObject* text;
PyObject* tail;
PyObject* id;
+ if (_Py_EnterRecursiveCall(" in Element.__deepcopy__")) {
+ return NULL;
+ }
+
PyTypeObject *tp = Py_TYPE(self);
elementtreestate *st = get_elementtree_state_by_type(tp);
// The deepcopy() helper takes care of incrementing the refcount
// of the object to copy so to avoid use-after-frees.
tag = deepcopy(st, self->tag, memo);
- if (!tag)
- return NULL;
+ if (!tag) {
+ goto error;
+ }
if (self->extra && self->extra->attrib) {
attrib = deepcopy(st, self->extra->attrib, memo);
if (!attrib) {
Py_DECREF(tag);
- return NULL;
+ goto error;
}
} else {
attrib = NULL;
@@ -832,8 +838,9 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
Py_DECREF(tag);
Py_XDECREF(attrib);
- if (!element)
- return NULL;
+ if (!element) {
+ goto error;
+ }
text = deepcopy(st, JOIN_OBJ(self->text), memo);
if (!text)
@@ -895,10 +902,12 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
if (i < 0)
goto error;
+ _Py_LeaveRecursiveCall();
return (PyObject*) element;
error:
- Py_DECREF(element);
+ _Py_LeaveRecursiveCall();
+ Py_XDECREF(element);
return NULL;
}
From 5b33424120430f60dda6fd293f199ecd94811fba Mon Sep 17 00:00:00 2001
From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Date: Thu, 23 Apr 2026 00:41:40 +0300
Subject: [PATCH 065/129] [3.14] Add a new Sphinx `soft-deprecated` directive
(GH-148630) (#148714)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Stan Ulbrych
---
Doc/c-api/allocation.rst | 12 ++---
Doc/c-api/file.rst | 9 ++--
Doc/c-api/frame.rst | 22 +++-------
Doc/c-api/long.rst | 4 +-
Doc/c-api/module.rst | 4 +-
Doc/c-api/monitoring.rst | 4 +-
Doc/c-api/sequence.rst | 5 +--
Doc/library/mimetypes.rst | 2 +-
Doc/library/os.rst | 10 ++---
Doc/tools/extensions/changes.py | 78 ++++++++++++++++++++++++++++++++-
Doc/tools/templates/dummy.html | 1 +
11 files changed, 107 insertions(+), 44 deletions(-)
diff --git a/Doc/c-api/allocation.rst b/Doc/c-api/allocation.rst
index 59044d2d88cc168..5f7a9152eccfc0a 100644
--- a/Doc/c-api/allocation.rst
+++ b/Doc/c-api/allocation.rst
@@ -2,7 +2,7 @@
.. _allocating-objects:
-Allocating Objects on the Heap
+Allocating objects on the heap
==============================
@@ -153,10 +153,12 @@ Allocating Objects on the Heap
To allocate and create extension modules.
-Deprecated aliases
-^^^^^^^^^^^^^^^^^^
+Soft-deprecated aliases
+^^^^^^^^^^^^^^^^^^^^^^^
-These are :term:`soft deprecated` aliases to existing functions and macros.
+.. soft-deprecated:: 3.10
+
+These are aliases to existing functions and macros.
They exist solely for backwards compatibility.
@@ -164,7 +166,7 @@ They exist solely for backwards compatibility.
:widths: auto
:header-rows: 1
- * * Deprecated alias
+ * * Soft-deprecated alias
* Function
* * .. c:macro:: PyObject_NEW(type, typeobj)
* :c:macro:`PyObject_New`
diff --git a/Doc/c-api/file.rst b/Doc/c-api/file.rst
index d89072ab24e241d..dcafefdc0458722 100644
--- a/Doc/c-api/file.rst
+++ b/Doc/c-api/file.rst
@@ -2,7 +2,7 @@
.. _fileobjects:
-File Objects
+File objects
------------
.. index:: pair: object; file
@@ -136,11 +136,12 @@ the :mod:`io` APIs instead.
failure; the appropriate exception will be set.
-Deprecated API
-^^^^^^^^^^^^^^
+Soft-deprecated API
+^^^^^^^^^^^^^^^^^^^
+.. soft-deprecated:: 3.15
-These are :term:`soft deprecated` APIs that were included in Python's C API
+These are APIs that were included in Python's C API
by mistake. They are documented solely for completeness; use other
``PyFile*`` APIs instead.
diff --git a/Doc/c-api/frame.rst b/Doc/c-api/frame.rst
index 967cfc727655ecb..4159ff6e5965fbd 100644
--- a/Doc/c-api/frame.rst
+++ b/Doc/c-api/frame.rst
@@ -1,6 +1,6 @@
.. highlight:: c
-Frame Objects
+Frame objects
-------------
.. c:type:: PyFrameObject
@@ -147,7 +147,7 @@ See also :ref:`Reflection `.
Return the line number that *frame* is currently executing.
-Frame Locals Proxies
+Frame locals proxies
^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 3.13
@@ -169,7 +169,7 @@ See :pep:`667` for more information.
Return non-zero if *obj* is a frame :func:`locals` proxy.
-Legacy Local Variable APIs
+Legacy local variable APIs
^^^^^^^^^^^^^^^^^^^^^^^^^^
These APIs are :term:`soft deprecated`. As of Python 3.13, they do nothing.
@@ -178,40 +178,34 @@ They exist solely for backwards compatibility.
.. c:function:: void PyFrame_LocalsToFast(PyFrameObject *f, int clear)
- This function is :term:`soft deprecated` and does nothing.
-
Prior to Python 3.13, this function would copy the :attr:`~frame.f_locals`
attribute of *f* to the internal "fast" array of local variables, allowing
changes in frame objects to be visible to the interpreter. If *clear* was
true, this function would process variables that were unset in the locals
dictionary.
- .. versionchanged:: 3.13
+ .. soft-deprecated:: 3.13
This function now does nothing.
.. c:function:: void PyFrame_FastToLocals(PyFrameObject *f)
- This function is :term:`soft deprecated` and does nothing.
-
Prior to Python 3.13, this function would copy the internal "fast" array
of local variables (which is used by the interpreter) to the
:attr:`~frame.f_locals` attribute of *f*, allowing changes in local
variables to be visible to frame objects.
- .. versionchanged:: 3.13
+ .. soft-deprecated:: 3.13
This function now does nothing.
.. c:function:: int PyFrame_FastToLocalsWithError(PyFrameObject *f)
- This function is :term:`soft deprecated` and does nothing.
-
Prior to Python 3.13, this function was similar to
:c:func:`PyFrame_FastToLocals`, but would return ``0`` on success, and
``-1`` with an exception set on failure.
- .. versionchanged:: 3.13
+ .. soft-deprecated:: 3.13
This function now does nothing.
@@ -219,7 +213,7 @@ They exist solely for backwards compatibility.
:pep:`667`
-Internal Frames
+Internal frames
^^^^^^^^^^^^^^^
Unless using :pep:`523`, you will not need this.
@@ -249,5 +243,3 @@ Unless using :pep:`523`, you will not need this.
Return the currently executing line number, or -1 if there is no line number.
.. versionadded:: 3.12
-
-
diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst
index acc3f9668fdbaf2..31cf1abf0c33813 100644
--- a/Doc/c-api/long.rst
+++ b/Doc/c-api/long.rst
@@ -197,12 +197,10 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
.. c:function:: long PyLong_AS_LONG(PyObject *obj)
- A :term:`soft deprecated` alias.
Exactly equivalent to the preferred ``PyLong_AsLong``. In particular,
it can fail with :exc:`OverflowError` or another exception.
- .. deprecated:: 3.14
- The function is soft deprecated.
+ .. soft-deprecated:: 3.14
.. c:function:: int PyLong_AsInt(PyObject *obj)
diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst
index 49ac4de6655a881..5a562721ce5df42 100644
--- a/Doc/c-api/module.rst
+++ b/Doc/c-api/module.rst
@@ -604,9 +604,7 @@ or code that creates modules dynamically.
// PyModule_AddObject() stole a reference to obj:
// Py_XDECREF(obj) is not needed here.
- .. deprecated:: 3.13
-
- :c:func:`PyModule_AddObject` is :term:`soft deprecated`.
+ .. soft-deprecated:: 3.13
.. c:function:: int PyModule_AddIntConstant(PyObject *module, const char *name, long value)
diff --git a/Doc/c-api/monitoring.rst b/Doc/c-api/monitoring.rst
index b0227c2f4faf15c..4bfcb86abf58ed1 100644
--- a/Doc/c-api/monitoring.rst
+++ b/Doc/c-api/monitoring.rst
@@ -205,6 +205,4 @@ would typically correspond to a Python function.
.. versionadded:: 3.13
- .. deprecated:: 3.14
-
- This function is :term:`soft deprecated`.
+ .. soft-deprecated:: 3.14
diff --git a/Doc/c-api/sequence.rst b/Doc/c-api/sequence.rst
index df5bf6b64a93a00..6bae8f25ad75d15 100644
--- a/Doc/c-api/sequence.rst
+++ b/Doc/c-api/sequence.rst
@@ -109,9 +109,8 @@ Sequence Protocol
Alias for :c:func:`PySequence_Contains`.
- .. deprecated:: 3.14
- The function is :term:`soft deprecated` and should no longer be used to
- write new code.
+ .. soft-deprecated:: 3.14
+ The function should no longer be used to write new code.
.. c:function:: Py_ssize_t PySequence_Index(PyObject *o, PyObject *value)
diff --git a/Doc/library/mimetypes.rst b/Doc/library/mimetypes.rst
index f489b60af3cb44c..81212b1fea93266 100644
--- a/Doc/library/mimetypes.rst
+++ b/Doc/library/mimetypes.rst
@@ -56,7 +56,7 @@ the information :func:`init` sets up.
.. versionchanged:: 3.8
Added support for *url* being a :term:`path-like object`.
- .. deprecated:: 3.13
+ .. soft-deprecated:: 3.13
Passing a file path instead of URL is :term:`soft deprecated`.
Use :func:`guess_file_type` for this.
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 721f18e6af74988..8a0a5a0d74b7f13 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -4711,9 +4711,8 @@ written in Python, such as a mail server's external command delivery program.
Use :class:`subprocess.Popen` or :func:`subprocess.run` to
control options like encodings.
- .. deprecated:: 3.14
- The function is :term:`soft deprecated` and should no longer be used to
- write new code. The :mod:`subprocess` module is recommended instead.
+ .. soft-deprecated:: 3.14
+ The :mod:`subprocess` module is recommended instead.
.. function:: posix_spawn(path, argv, env, *, file_actions=None, \
@@ -4941,9 +4940,8 @@ written in Python, such as a mail server's external command delivery program.
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.
- .. deprecated:: 3.14
- These functions are :term:`soft deprecated` and should no longer be used
- to write new code. The :mod:`subprocess` module is recommended instead.
+ .. soft-deprecated:: 3.14
+ The :mod:`subprocess` module is recommended instead.
.. data:: P_NOWAIT
diff --git a/Doc/tools/extensions/changes.py b/Doc/tools/extensions/changes.py
index 8de5e7f78c66272..02dc51b3a76943a 100644
--- a/Doc/tools/extensions/changes.py
+++ b/Doc/tools/extensions/changes.py
@@ -2,8 +2,10 @@
from __future__ import annotations
-from typing import TYPE_CHECKING
+import re
+from docutils import nodes
+from sphinx import addnodes
from sphinx.domains.changeset import (
VersionChange,
versionlabel_classes,
@@ -11,6 +13,7 @@
)
from sphinx.locale import _ as sphinx_gettext
+TYPE_CHECKING = False
if TYPE_CHECKING:
from docutils.nodes import Node
from sphinx.application import Sphinx
@@ -73,6 +76,76 @@ def run(self) -> list[Node]:
versionlabel_classes[self.name] = ""
+class SoftDeprecated(PyVersionChange):
+ """Directive for soft deprecations that auto-links to the glossary term.
+
+ Usage::
+
+ .. soft-deprecated:: 3.15
+
+ Use :func:`new_thing` instead.
+
+ Renders as: "Soft deprecated since version 3.15: Use new_thing() instead."
+ with "Soft deprecated" linking to the glossary definition.
+ """
+
+ _TERM_RE = re.compile(r":term:`([^`]+)`")
+
+ def run(self) -> list[Node]:
+ versionlabels[self.name] = sphinx_gettext(
+ ":term:`Soft deprecated` since version %s"
+ )
+ versionlabel_classes[self.name] = "soft-deprecated"
+ try:
+ result = super().run()
+ finally:
+ versionlabels[self.name] = ""
+ versionlabel_classes[self.name] = ""
+
+ for node in result:
+ # Add "versionchanged" class so existing theme CSS applies
+ node["classes"] = node.get("classes", []) + ["versionchanged"]
+ # Replace the plain-text "Soft deprecated" with a glossary reference
+ for inline in node.findall(nodes.inline):
+ if "versionmodified" in inline.get("classes", []):
+ self._add_glossary_link(inline)
+
+ return result
+
+ @classmethod
+ def _add_glossary_link(cls, inline: nodes.inline) -> None:
+ """Replace :term:`soft deprecated` text with a cross-reference to the
+ 'Soft deprecated' glossary entry."""
+ for child in inline.children:
+ if not isinstance(child, nodes.Text):
+ continue
+
+ text = str(child)
+ match = cls._TERM_RE.search(text)
+ if match is None:
+ continue
+
+ ref = addnodes.pending_xref(
+ "",
+ nodes.Text(match.group(1)),
+ refdomain="std",
+ reftype="term",
+ reftarget="soft deprecated",
+ refwarn=True,
+ )
+
+ start, end = match.span()
+ new_nodes: list[nodes.Node] = []
+ if start > 0:
+ new_nodes.append(nodes.Text(text[:start]))
+ new_nodes.append(ref)
+ if end < len(text):
+ new_nodes.append(nodes.Text(text[end:]))
+
+ child.parent.replace(child, new_nodes)
+ break
+
+
def setup(app: Sphinx) -> ExtensionMetadata:
# Override Sphinx's directives with support for 'next'
app.add_directive("versionadded", PyVersionChange, override=True)
@@ -83,6 +156,9 @@ def setup(app: Sphinx) -> ExtensionMetadata:
# Register the ``.. deprecated-removed::`` directive
app.add_directive("deprecated-removed", DeprecatedRemoved)
+ # Register the ``.. soft-deprecated::`` directive
+ app.add_directive("soft-deprecated", SoftDeprecated)
+
return {
"version": "1.0",
"parallel_read_safe": True,
diff --git a/Doc/tools/templates/dummy.html b/Doc/tools/templates/dummy.html
index 75f6607d8f3698d..699e518801cbcd7 100644
--- a/Doc/tools/templates/dummy.html
+++ b/Doc/tools/templates/dummy.html
@@ -29,6 +29,7 @@
{% trans %}Deprecated since version %s, will be removed in version %s{% endtrans %}
{% trans %}Deprecated since version %s, removed in version %s{% endtrans %}
+{% trans %}:term:`Soft deprecated` since version %s{% endtrans %}
In docsbuild-scripts, when rewriting indexsidebar.html with actual versions:
From 3c71d3654e285ec84391b34266d0175e294a222c Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 04:45:46 +0200
Subject: [PATCH 066/129] [3.14] gh-145194: Fix typing in re tokenizer example
(GH-145198) (#148897)
(cherry picked from commit bd7352d8071dc00531f2c527977602729f2d3ec6)
Co-authored-by: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com>
---
Doc/library/re.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Doc/library/re.rst b/Doc/library/re.rst
index 6ee52c6927d91fa..5b8d9cdc671abda 100644
--- a/Doc/library/re.rst
+++ b/Doc/library/re.rst
@@ -1883,7 +1883,7 @@ successive matches::
class Token(NamedTuple):
type: str
- value: str
+ value: int | float | str
line: int
column: int
From 27cd23470a3c69f3e1e9554721a0c3ff7c5b3449 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 04:57:38 +0200
Subject: [PATCH 067/129] [3.14] gh-146553: Fix infinite loop in
typing.get_type_hints() on circular __wrapped__ (GH-148595) (#148895)
(cherry picked from commit be833e658aaf6703b0dd0c0dadb893d72cbe4c77)
Co-authored-by: Shamil
---
Lib/test/test_typing.py | 18 ++++++++++++++++++
Lib/typing.py | 4 ++++
...6-04-15-11-00-39.gh-issue-146553.VGOsoP.rst | 2 ++
3 files changed, 24 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index b660368144b1cd3..b30e30d1751ed7d 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -6778,6 +6778,24 @@ def test_get_type_hints_wrapped_decoratored_func(self):
self.assertEqual(gth(ForRefExample.func), expects)
self.assertEqual(gth(ForRefExample.nested), expects)
+ def test_get_type_hints_wrapped_cycle_self(self):
+ # gh-146553: __wrapped__ self-reference must raise ValueError,
+ # not loop forever.
+ def f(x: int) -> str: ...
+ f.__wrapped__ = f
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ get_type_hints(f)
+
+ def test_get_type_hints_wrapped_cycle_mutual(self):
+ # gh-146553: mutual __wrapped__ cycle (a -> b -> a) must raise
+ # ValueError, not loop forever.
+ def a(): ...
+ def b(): ...
+ a.__wrapped__ = b
+ b.__wrapped__ = a
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ get_type_hints(a)
+
def test_get_type_hints_annotated(self):
def foobar(x: List['X']): ...
X = Annotated[int, (1, 10)]
diff --git a/Lib/typing.py b/Lib/typing.py
index 380211183a41335..5da5cb0d22f1484 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2416,8 +2416,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
else:
nsobj = obj
# Find globalns for the unwrapped object.
+ seen = {id(nsobj)}
while hasattr(nsobj, '__wrapped__'):
nsobj = nsobj.__wrapped__
+ if id(nsobj) in seen:
+ raise ValueError(f'wrapper loop when unwrapping {obj!r}')
+ seen.add(id(nsobj))
globalns = getattr(nsobj, '__globals__', {})
if localns is None:
localns = globalns
diff --git a/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst b/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst
new file mode 100644
index 000000000000000..44216318d474a9f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-15-11-00-39.gh-issue-146553.VGOsoP.rst
@@ -0,0 +1,2 @@
+Fix infinite loop in :func:`typing.get_type_hints` when ``__wrapped__``
+forms a cycle. Patch by Shamil Abdulaev.
From 0a63bb89717e55bc39e7a574bf29d9cf767c3032 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 04:57:54 +0200
Subject: [PATCH 068/129] [3.14] gh-142965: Fix Concatenate documentation to
reflect valid use cases (GH-143316) (#148899)
The documentation previously stated that Concatenate is only valid
when used as the first argument to Callable, but according to PEP 612,
it can also be used when instantiating user-defined generic classes
with ParamSpec parameters.
(cherry picked from commit 75ff1afcb6a1bb2b3d54899e9b222a61798fa491)
Co-authored-by: John Seong <39040639+sandole@users.noreply.github.com>
---
Doc/library/typing.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index a2625ab2a371663..40f8c130d801db0 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -1174,7 +1174,8 @@ These can be used as types in annotations. They all support subscription using
or transforms parameters of another
callable. Usage is in the form
``Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable]``. ``Concatenate``
- is currently only valid when used as the first argument to a :ref:`Callable `.
+ is valid when used in :ref:`Callable ` type hints
+ and when instantiating user-defined generic classes with :class:`ParamSpec` parameters.
The last parameter to ``Concatenate`` must be a :class:`ParamSpec` or
ellipsis (``...``).
From 032b1be5bb88f0988a13b056f1a09253acc0d838 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 04:59:18 +0200
Subject: [PATCH 069/129] [3.14] gh-119180: Document the `format` parameter in
`typing.get_type_hints()` (GH-143758) (#148901)
Do not mention `__annotations__` dictionaries, as this is slightly
outdated since 3.14.
Rewrite the note about possible exceptions for clarity. Also do not
mention imported type aliases, as since 3.12 aliases with the `type`
statement do not suffer from this limitation anymore.
(cherry picked from commit 8bf99ae3a9f12d105a70d6fda93dddde4adeee8f)
Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com>
---
Doc/library/typing.rst | 31 +++++++++++++++++--------------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 40f8c130d801db0..7a76bc5545724b7 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -3346,13 +3346,13 @@ Functions and decorators
Introspection helpers
---------------------
-.. function:: get_type_hints(obj, globalns=None, localns=None, include_extras=False)
+.. function:: get_type_hints(obj, globalns=None, localns=None, include_extras=False, *, format=Format.VALUE)
Return a dictionary containing type hints for a function, method, module,
class object, or other callable object.
- This is often the same as ``obj.__annotations__``, but this function makes
- the following changes to the annotations dictionary:
+ This is often the same as :func:`annotationlib.get_annotations`, but this
+ function makes the following changes to the annotations dictionary:
* Forward references encoded as string literals or :class:`ForwardRef`
objects are handled by evaluating them in *globalns*, *localns*, and
@@ -3366,17 +3366,15 @@ Introspection helpers
annotations from ``C``'s base classes with those on ``C`` directly. This
is done by traversing :attr:`C.__mro__ ` and iteratively
combining
- ``__annotations__`` dictionaries. Annotations on classes appearing
- earlier in the :term:`method resolution order` always take precedence over
- annotations on classes appearing later in the method resolution order.
+ :term:`annotations ` of each base class. Annotations
+ on classes appearing earlier in the :term:`method resolution order` always
+ take precedence over annotations on classes appearing later in the method
+ resolution order.
* The function recursively replaces all occurrences of
``Annotated[T, ...]``, ``Required[T]``, ``NotRequired[T]``, and ``ReadOnly[T]``
with ``T``, unless *include_extras* is set to ``True`` (see
:class:`Annotated` for more information).
- See also :func:`annotationlib.get_annotations`, a lower-level function that
- returns annotations more directly.
-
.. caution::
This function may execute arbitrary code contained in annotations.
@@ -3384,11 +3382,12 @@ Introspection helpers
.. note::
- If any forward references in the annotations of *obj* are not resolvable
- or are not valid Python code, this function will raise an exception
- such as :exc:`NameError`. For example, this can happen with imported
- :ref:`type aliases ` that include forward references,
- or with names imported under :data:`if TYPE_CHECKING `.
+ If :attr:`Format.VALUE ` is used and any
+ forward references in the annotations of *obj* are not resolvable, a
+ :exc:`NameError` exception is raised. For example, this can happen
+ with names imported under :data:`if TYPE_CHECKING `.
+ More generally, any kind of exception can be raised if an annotation
+ contains invalid Python code.
.. note::
@@ -3406,6 +3405,10 @@ Introspection helpers
if a default value equal to ``None`` was set.
Now the annotation is returned unchanged.
+ .. versionchanged:: 3.14
+ Added the ``format`` parameter. See the documentation on
+ :func:`annotationlib.get_annotations` for more information.
+
.. versionchanged:: 3.14
Calling :func:`get_type_hints` on instances is no longer supported.
Some instances were accepted in earlier versions as an undocumented
From dd9a77ff2e24f4c7e5b5af04dd679b856a76ff32 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 06:06:39 +0200
Subject: [PATCH 070/129] [3.14] gh-148892: Drop mention of deprecated
cibuildwheel option (GH-148893) (#148903)
gh-148892: Drop mention of deprecated cibuildwheel option (GH-148893)
(cherry picked from commit 3b9397988d1f83740e7d73d17d56767976a583b4)
Co-authored-by: Nathan Goldbaum
---
Doc/howto/free-threading-extensions.rst | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst
index fe960f90e44168a..43b8ff26ebde1ad 100644
--- a/Doc/howto/free-threading-extensions.rst
+++ b/Doc/howto/free-threading-extensions.rst
@@ -395,11 +395,9 @@ C API extensions need to be built specifically for the free-threaded build.
The wheels, shared libraries, and binaries are indicated by a ``t`` suffix.
* `pypa/manylinux `_ supports the
- free-threaded build, with the ``t`` suffix, such as ``python3.13t``.
-* `pypa/cibuildwheel `_ supports the
- free-threaded build on Python 3.13 and 3.14. On Python 3.14, free-threaded
- wheels will be built by default. On Python 3.13, you will need to set
- `CIBW_ENABLE to cpython-freethreading `_.
+ free-threaded build, with the ``t`` suffix, such as ``python3.14t``.
+* `pypa/cibuildwheel `_ supports
+ building wheels for the free-threaded build of Python 3.14 and newer.
Limited C API and Stable ABI
............................
From f795e042043dfe26c42e1971d4502c1cdc4c65b8 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 15:05:09 +0200
Subject: [PATCH 071/129] [3.14] gh-90309: Base64-encode cookie values embedded
in JS (GH-148889)
(cherry picked from commit 76b3923d688c0efc580658476c5f525ec8735104)
Co-authored-by: Seth Larson
---
Lib/http/cookies.py | 8 +++--
Lib/test/test_http_cookies.py | 29 ++++++++++++-------
...6-04-21-13-46-30.gh-issue-90309.srvj9q.rst | 3 ++
3 files changed, 27 insertions(+), 13 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst
diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
index d5b8ba939bee7c5..5c5b14788dc2f09 100644
--- a/Lib/http/cookies.py
+++ b/Lib/http/cookies.py
@@ -391,17 +391,21 @@ def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
def js_output(self, attrs=None):
+ import base64
# Print javascript
output_string = self.OutputString(attrs)
if _has_control_character(output_string):
raise CookieError("Control characters are not allowed in cookies")
+ # Base64-encode value to avoid template
+ # injection in cookie values.
+ output_encoded = base64.b64encode(output_string.encode('utf-8')).decode("ascii")
return """
- """ % (output_string.replace('"', r'\"'))
+ """ % (output_encoded,)
def OutputString(self, attrs=None):
# Build up our result
diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
index 33da395717deb01..4884b07c95b9c50 100644
--- a/Lib/test/test_http_cookies.py
+++ b/Lib/test/test_http_cookies.py
@@ -1,5 +1,5 @@
# Simple test suite for http/cookies.py
-
+import base64
import copy
import unittest
import doctest
@@ -152,17 +152,19 @@ def test_load(self):
self.assertEqual(C.output(['path']),
'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
- self.assertEqual(C.js_output(), r"""
+ cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme; Version=1').decode('ascii')
+ self.assertEqual(C.js_output(), fr"""
""")
- self.assertEqual(C.js_output(['path']), r"""
+ cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme').decode('ascii')
+ self.assertEqual(C.js_output(['path']), fr"""
""")
@@ -267,17 +269,19 @@ def test_quoted_meta(self):
self.assertEqual(C.output(['path']),
'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
- self.assertEqual(C.js_output(), r"""
+ expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1').decode('ascii')
+ self.assertEqual(C.js_output(), fr"""
""")
- self.assertEqual(C.js_output(['path']), r"""
+ expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme').decode('ascii')
+ self.assertEqual(C.js_output(['path']), fr"""
""")
@@ -368,13 +372,16 @@ def test_setter(self):
self.assertEqual(
M.output(),
"Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
+ expected_encoded_cookie = base64.b64encode(
+ ("%s=%s; Path=/foo" % (i, "%s_coded_val" % i)).encode("ascii")
+ ).decode('ascii')
expected_js_output = """
- """ % (i, "%s_coded_val" % i)
+ """ % (expected_encoded_cookie,)
self.assertEqual(M.js_output(), expected_js_output)
for i in ["foo bar", "foo@bar"]:
# Try some illegal characters
diff --git a/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst b/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst
new file mode 100644
index 000000000000000..d7d376737e4ad11
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst
@@ -0,0 +1,3 @@
+Base64-encode values when embedding cookies to JavaScript using the
+:meth:`http.cookies.BaseCookie.js_output` method to avoid injection
+and escaping.
From b6a7212edb3153373711c05004e154e7a5bd5068 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 15:47:52 +0200
Subject: [PATCH 072/129] [3.14] gh-148680: Replace internal names with
type_reprs of objects in string representations of ForwardRef (GH-148682)
(#148913)
(cherry picked from commit 158dbbb97fffbc47eb446d2b1576ce887e5c1802)
Co-authored-by: David Ellis
Co-authored-by: Shamil
---
Lib/annotationlib.py | 36 +++++++++++++++++--
Lib/test/test_annotationlib.py | 20 +++++++++++
...-04-23-07-38-04.gh-issue-148680.___ePl.rst | 1 +
3 files changed, 55 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-04-23-07-38-04.gh-issue-148680.___ePl.rst
diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 9fee25641143390..5c9a0812646f814 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -47,6 +47,7 @@ class Format(enum.IntEnum):
"__cell__",
"__owner__",
"__stringifier_dict__",
+ "__resolved_str_cache__",
)
@@ -94,6 +95,7 @@ def __init__(
# value later.
self.__code__ = None
self.__ast_node__ = None
+ self.__resolved_str_cache__ = None
def __init_subclass__(cls, /, *args, **kwds):
raise TypeError("Cannot subclass ForwardRef")
@@ -113,7 +115,7 @@ def evaluate(
"""
match format:
case Format.STRING:
- return self.__forward_arg__
+ return self.__resolved_str__
case Format.VALUE:
is_forwardref_format = False
case Format.FORWARDREF:
@@ -258,6 +260,24 @@ def __forward_arg__(self):
"Attempted to access '__forward_arg__' on an uninitialized ForwardRef"
)
+ @property
+ def __resolved_str__(self):
+ # __forward_arg__ with any names from __extra_names__ replaced
+ # with the type_repr of the value they represent
+ if self.__resolved_str_cache__ is None:
+ resolved_str = self.__forward_arg__
+ names = self.__extra_names__
+
+ if names:
+ visitor = _ExtraNameFixer(names)
+ ast_expr = ast.parse(resolved_str, mode="eval").body
+ node = visitor.visit(ast_expr)
+ resolved_str = ast.unparse(node)
+
+ self.__resolved_str_cache__ = resolved_str
+
+ return self.__resolved_str_cache__
+
@property
def __forward_code__(self):
if self.__code__ is not None:
@@ -321,7 +341,7 @@ def __repr__(self):
extra.append(", is_class=True")
if self.__owner__ is not None:
extra.append(f", owner={self.__owner__!r}")
- return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
+ return f"ForwardRef({self.__resolved_str__!r}{''.join(extra)})"
_Template = type(t"")
@@ -357,6 +377,7 @@ def __init__(
self.__cell__ = cell
self.__owner__ = owner
self.__stringifier_dict__ = stringifier_dict
+ self.__resolved_str_cache__ = None # Needed for ForwardRef
def __convert_to_ast(self, other):
if isinstance(other, _Stringifier):
@@ -1163,3 +1184,14 @@ def _get_dunder_annotations(obj):
if not isinstance(ann, dict):
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
return ann
+
+
+class _ExtraNameFixer(ast.NodeTransformer):
+ """Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation"""
+ def __init__(self, extra_names):
+ self.extra_names = extra_names
+
+ def visit_Name(self, node: ast.Name):
+ if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel:
+ node = ast.Name(id=type_repr(new_name))
+ return node
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index 50cf8fcb6b4ed60..77f2a77882fce25 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -1961,6 +1961,15 @@ def test_forward_repr(self):
"typing.List[ForwardRef('int', owner='class')]",
)
+ def test_forward_repr_extra_names(self):
+ def f(a: undefined | str): ...
+
+ annos = get_annotations(f, format=Format.FORWARDREF)
+
+ self.assertRegex(
+ repr(annos['a']), r"ForwardRef\('undefined \| str'.*\)"
+ )
+
def test_forward_recursion_actually(self):
def namespace1():
a = ForwardRef("A")
@@ -2037,6 +2046,17 @@ def test_evaluate_string_format(self):
fr = ForwardRef("set[Any]")
self.assertEqual(fr.evaluate(format=Format.STRING), "set[Any]")
+ def test_evaluate_string_format_extra_names(self):
+ # Test that internal extra_names are replaced when evaluating as strings
+ def f(a: unknown | str | int | list[str] | tuple[int, ...]): ...
+
+ fr = get_annotations(f, format=Format.FORWARDREF)['a']
+ # Test the cache is not populated before access
+ self.assertIsNone(fr.__resolved_str_cache__)
+
+ self.assertEqual(fr.evaluate(format=Format.STRING), "unknown | str | int | list[str] | tuple[int, ...]")
+ self.assertEqual(fr.__resolved_str_cache__, "unknown | str | int | list[str] | tuple[int, ...]")
+
def test_evaluate_forwardref_format(self):
fr = ForwardRef("undef")
evaluated = fr.evaluate(format=Format.FORWARDREF)
diff --git a/Misc/NEWS.d/next/Library/2026-04-23-07-38-04.gh-issue-148680.___ePl.rst b/Misc/NEWS.d/next/Library/2026-04-23-07-38-04.gh-issue-148680.___ePl.rst
new file mode 100644
index 000000000000000..d3790079545a072
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-23-07-38-04.gh-issue-148680.___ePl.rst
@@ -0,0 +1 @@
+``ForwardRef`` objects that contain internal names to represent known objects now show the ``type_repr`` of the known object rather than the internal ``__annotationlib_name_x__`` name when evaluated as strings.
From 5e758ff5252bf4460ffa4bf78d0a41cadefe3bd7 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 15:57:55 +0200
Subject: [PATCH 073/129] [3.14] gh-148464: Add missing ``__ctype_le/be__``
attributes for complex types in the ctype module (GH-148485) (GH-148677)
(cherry picked from commit 769cc8338f35eb134508aca701a59342bcb6a84b)
Co-authored-by: Sergey B Kirpichev
Co-authored-by: sunmy2019 <59365878+sunmy2019@users.noreply.github.com>
---
Lib/test/test_ctypes/test_byteswap.py | 42 ++++++++++
...-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst | 3 +
Modules/_ctypes/_ctypes.c | 45 +++++++----
Modules/_ctypes/cfield.c | 78 +++++++++++++++++++
4 files changed, 154 insertions(+), 14 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst
diff --git a/Lib/test/test_ctypes/test_byteswap.py b/Lib/test/test_ctypes/test_byteswap.py
index ea5951603f93245..6a1bae14773d277 100644
--- a/Lib/test/test_ctypes/test_byteswap.py
+++ b/Lib/test/test_ctypes/test_byteswap.py
@@ -166,6 +166,48 @@ def test_endian_double(self):
self.assertEqual(s.value, math.pi)
self.assertEqual(bin(struct.pack(">d", math.pi)), bin(s))
+ @unittest.skipUnless(hasattr(ctypes, 'c_float_complex'), "No complex types")
+ def test_endian_float_complex(self):
+ c_float_complex = ctypes.c_float_complex
+ if sys.byteorder == "little":
+ self.assertIs(c_float_complex.__ctype_le__, c_float_complex)
+ self.assertIs(c_float_complex.__ctype_be__.__ctype_le__,
+ c_float_complex)
+ else:
+ self.assertIs(c_float_complex.__ctype_be__, c_float_complex)
+ self.assertIs(c_float_complex.__ctype_le__.__ctype_be__,
+ c_float_complex)
+ s = c_float_complex(math.pi+1j)
+ self.assertEqual(bin(struct.pack("F", math.pi+1j)), bin(s))
+ self.assertAlmostEqual(s.value, math.pi+1j, places=6)
+ s = c_float_complex.__ctype_le__(math.pi+1j)
+ self.assertAlmostEqual(s.value, math.pi+1j, places=6)
+ self.assertEqual(bin(struct.pack("F", math.pi+1j)), bin(s))
+
+ @unittest.skipUnless(hasattr(ctypes, 'c_double_complex'), "No complex types")
+ def test_endian_double_complex(self):
+ c_double_complex = ctypes.c_double_complex
+ if sys.byteorder == "little":
+ self.assertIs(c_double_complex.__ctype_le__, c_double_complex)
+ self.assertIs(c_double_complex.__ctype_be__.__ctype_le__,
+ c_double_complex)
+ else:
+ self.assertIs(c_double_complex.__ctype_be__, c_double_complex)
+ self.assertIs(c_double_complex.__ctype_le__.__ctype_be__,
+ c_double_complex)
+ s = c_double_complex(math.pi+1j)
+ self.assertEqual(bin(struct.pack("D", math.pi+1j)), bin(s))
+ self.assertAlmostEqual(s.value, math.pi+1j, places=6)
+ s = c_double_complex.__ctype_le__(math.pi+1j)
+ self.assertAlmostEqual(s.value, math.pi+1j, places=6)
+ self.assertEqual(bin(struct.pack("D", math.pi+1j)), bin(s))
+
def test_endian_other(self):
self.assertIs(c_byte.__ctype_le__, c_byte)
self.assertIs(c_byte.__ctype_be__, c_byte)
diff --git a/Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst b/Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst
new file mode 100644
index 000000000000000..85b99531d033b1c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst
@@ -0,0 +1,3 @@
+Add missing ``__ctype_le/be__`` attributes for
+:class:`~ctypes.c_float_complex` and :class:`~ctypes.c_double_complex`. Patch
+by Sergey B Kirpichev.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 3d6e1bd28587243..705aac896aab4e2 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -2232,6 +2232,31 @@ c_void_p_from_param_impl(PyObject *type, PyTypeObject *cls, PyObject *value)
return NULL;
}
+static int
+set_stginfo_ffi_type_pointer(StgInfo *stginfo, struct fielddesc *fmt)
+{
+ if (!fmt->pffi_type->elements) {
+ stginfo->ffi_type_pointer = *fmt->pffi_type;
+ }
+ else {
+ /* From primitive types - only complex types have the elements
+ struct field as non-NULL (two element array). */
+ assert(fmt->pffi_type->type == FFI_TYPE_COMPLEX);
+ const size_t els_size = 2 * sizeof(ffi_type *);
+ stginfo->ffi_type_pointer.size = fmt->pffi_type->size;
+ stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment;
+ stginfo->ffi_type_pointer.type = fmt->pffi_type->type;
+ stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size);
+ if (!stginfo->ffi_type_pointer.elements) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ memcpy(stginfo->ffi_type_pointer.elements,
+ fmt->pffi_type->elements, els_size);
+ }
+ return 0;
+}
+
static PyMethodDef c_void_p_methods[] = {C_VOID_P_FROM_PARAM_METHODDEF {0}};
static PyMethodDef c_char_p_methods[] = {C_CHAR_P_FROM_PARAM_METHODDEF {0}};
static PyMethodDef c_wchar_p_methods[] = {C_WCHAR_P_FROM_PARAM_METHODDEF {0}};
@@ -2276,8 +2301,10 @@ static PyObject *CreateSwappedType(ctypes_state *st, PyTypeObject *type,
Py_DECREF(result);
return NULL;
}
-
- stginfo->ffi_type_pointer = *fmt->pffi_type;
+ if (set_stginfo_ffi_type_pointer(stginfo, fmt)) {
+ Py_DECREF(result);
+ return NULL;
+ }
stginfo->align = fmt->pffi_type->alignment;
stginfo->length = 0;
stginfo->size = fmt->pffi_type->size;
@@ -2372,18 +2399,8 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
if (!stginfo) {
goto error;
}
-
- if (!fmt->pffi_type->elements) {
- stginfo->ffi_type_pointer = *fmt->pffi_type;
- }
- else {
- const size_t els_size = sizeof(fmt->pffi_type->elements);
- stginfo->ffi_type_pointer.size = fmt->pffi_type->size;
- stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment;
- stginfo->ffi_type_pointer.type = fmt->pffi_type->type;
- stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size);
- memcpy(stginfo->ffi_type_pointer.elements,
- fmt->pffi_type->elements, els_size);
+ if (set_stginfo_ffi_type_pointer(stginfo, fmt)) {
+ goto error;
}
stginfo->align = fmt->pffi_type->alignment;
stginfo->length = 0;
diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index e0d172fa4354167..0f51eab41c93e16 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -792,6 +792,44 @@ D_get(void *ptr, Py_ssize_t size)
return PyComplex_FromDoubles(x[0], x[1]);
}
+static PyObject *
+D_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
+{
+ assert(NUM_BITS(size) || (size == 2*sizeof(double)));
+ Py_complex c = PyComplex_AsCComplex(value);
+
+ if (c.real == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+#ifdef WORDS_BIGENDIAN
+ if (PyFloat_Pack8(c.real, ptr, 1)
+ || PyFloat_Pack8(c.imag, ptr + sizeof(double), 1))
+ {
+ return NULL;
+ }
+#else
+ if (PyFloat_Pack8(c.real, ptr, 0)
+ || PyFloat_Pack8(c.imag, ptr + sizeof(double), 0))
+ {
+ return NULL;
+ }
+#endif
+ _RET(value);
+}
+
+static PyObject *
+D_get_sw(void *ptr, Py_ssize_t size)
+{
+ assert(NUM_BITS(size) || (size == 2*sizeof(double)));
+#ifdef WORDS_BIGENDIAN
+ return PyComplex_FromDoubles(PyFloat_Unpack8(ptr, 1),
+ PyFloat_Unpack8(ptr + sizeof(double), 1));
+#else
+ return PyComplex_FromDoubles(PyFloat_Unpack8(ptr, 0),
+ PyFloat_Unpack8(ptr + sizeof(double), 0));
+#endif
+}
+
/* F: float complex */
static PyObject *
F_set(void *ptr, PyObject *value, Py_ssize_t size)
@@ -817,6 +855,44 @@ F_get(void *ptr, Py_ssize_t size)
return PyComplex_FromDoubles(x[0], x[1]);
}
+static PyObject *
+F_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
+{
+ assert(NUM_BITS(size) || (size == 2*sizeof(float)));
+ Py_complex c = PyComplex_AsCComplex(value);
+
+ if (c.real == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+#ifdef WORDS_BIGENDIAN
+ if (PyFloat_Pack4(c.real, ptr, 1)
+ || PyFloat_Pack4(c.imag, ptr + sizeof(float), 1))
+ {
+ return NULL;
+ }
+#else
+ if (PyFloat_Pack4(c.real, ptr, 0)
+ || PyFloat_Pack4(c.imag, ptr + sizeof(float), 0))
+ {
+ return NULL;
+ }
+#endif
+ _RET(value);
+}
+
+static PyObject *
+F_get_sw(void *ptr, Py_ssize_t size)
+{
+ assert(NUM_BITS(size) || (size == 2*sizeof(float)));
+#ifdef WORDS_BIGENDIAN
+ return PyComplex_FromDoubles(PyFloat_Unpack4(ptr, 1),
+ PyFloat_Unpack4(ptr + sizeof(float), 1));
+#else
+ return PyComplex_FromDoubles(PyFloat_Unpack4(ptr, 0),
+ PyFloat_Unpack4(ptr + sizeof(float), 0));
+#endif
+}
+
/* G: long double complex */
static PyObject *
G_set(void *ptr, PyObject *value, Py_ssize_t size)
@@ -1602,7 +1678,9 @@ for base_code, base_c_type in [
#if defined(_Py_FFI_SUPPORT_C_COMPLEX)
if (Py_FFI_COMPLEX_AVAILABLE) {
TABLE_ENTRY(D, &ffi_type_complex_double);
+ TABLE_ENTRY_SW(D, &ffi_type_complex_double);
TABLE_ENTRY(F, &ffi_type_complex_float);
+ TABLE_ENTRY_SW(F, &ffi_type_complex_float);
TABLE_ENTRY(G, &ffi_type_complex_longdouble);
}
#endif
From 0f656e26410f92932eef53441baf1cffb23dc4ee Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 16:00:37 +0200
Subject: [PATCH 074/129] [3.14] gh-148484: Fix memory leak of iterator in
array.array constructor (GH-148523) (GH-148678)
(cherry picked from commit afde75664eb3ff3e147806f027c9da54c7eb77d4)
Co-authored-by: Gleb Popov
---
Modules/arraymodule.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index f91b00aeaa6da1e..67df64b2f95bc45 100644
--- a/Modules/arraymodule.c
+++ b/Modules/arraymodule.c
@@ -2854,8 +2854,10 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
len = 0;
a = newarrayobject(type, len, descr);
- if (a == NULL)
+ if (a == NULL) {
+ Py_XDECREF(it);
return NULL;
+ }
if (len > 0 && !array_Check(initial, state)) {
Py_ssize_t i;
@@ -2864,11 +2866,13 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
PySequence_GetItem(initial, i);
if (v == NULL) {
Py_DECREF(a);
+ Py_XDECREF(it);
return NULL;
}
if (setarrayitem(a, i, v) != 0) {
Py_DECREF(v);
Py_DECREF(a);
+ Py_XDECREF(it);
return NULL;
}
Py_DECREF(v);
@@ -2880,6 +2884,7 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
v = array_array_frombytes((PyObject *)a, initial);
if (v == NULL) {
Py_DECREF(a);
+ Py_XDECREF(it);
return NULL;
}
Py_DECREF(v);
@@ -2890,6 +2895,7 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
wchar_t *ustr = PyUnicode_AsWideCharString(initial, &n);
if (ustr == NULL) {
Py_DECREF(a);
+ Py_XDECREF(it);
return NULL;
}
@@ -2910,6 +2916,7 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Py_UCS4 *ustr = PyUnicode_AsUCS4Copy(initial);
if (ustr == NULL) {
Py_DECREF(a);
+ Py_XDECREF(it);
return NULL;
}
@@ -2937,6 +2944,7 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return a;
}
}
+ Py_XDECREF(it);
PyErr_SetString(PyExc_ValueError,
"bad typecode (must be b, B, u, w, h, H, i, I, l, L, q, Q, f or d)");
return NULL;
From 31ba91a35eb2e2a370734cdb4d3d3089a3272048 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Thu, 23 Apr 2026 18:32:29 +0200
Subject: [PATCH 075/129] [3.14] gh-132631: Fix "I/O operation on closed file"
when parsing JSON Lines file (GH-132632) (#148921)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Brian Schubert
---
Lib/json/tool.py | 3 ++-
Lib/test/test_json/json_lines.jsonl | 2 ++
Lib/test/test_json/test_tool.py | 9 +++++++++
.../2025-04-17-15-26-35.gh-issue-132631.IDFZfb.rst | 2 ++
4 files changed, 15 insertions(+), 1 deletion(-)
create mode 100644 Lib/test/test_json/json_lines.jsonl
create mode 100644 Misc/NEWS.d/next/Library/2025-04-17-15-26-35.gh-issue-132631.IDFZfb.rst
diff --git a/Lib/json/tool.py b/Lib/json/tool.py
index 1967817add8abce..0cabbdba85a1551 100644
--- a/Lib/json/tool.py
+++ b/Lib/json/tool.py
@@ -88,7 +88,8 @@ def main():
infile = open(options.infile, encoding='utf-8')
try:
if options.json_lines:
- objs = (json.loads(line) for line in infile)
+ lines = infile.readlines()
+ objs = (json.loads(line) for line in lines)
else:
objs = (json.load(infile),)
finally:
diff --git a/Lib/test/test_json/json_lines.jsonl b/Lib/test/test_json/json_lines.jsonl
new file mode 100644
index 000000000000000..d2f292111956f3a
--- /dev/null
+++ b/Lib/test/test_json/json_lines.jsonl
@@ -0,0 +1,2 @@
+{"ingredients":["frog", "water", "chocolate", "glucose"]}
+{"ingredients":["chocolate","steel bolts"]}
diff --git a/Lib/test/test_json/test_tool.py b/Lib/test/test_json/test_tool.py
index 7b5d217a21558c6..0a96b318b15b1c9 100644
--- a/Lib/test/test_json/test_tool.py
+++ b/Lib/test/test_json/test_tool.py
@@ -1,4 +1,5 @@
import errno
+import pathlib
import os
import sys
import textwrap
@@ -157,6 +158,14 @@ def test_jsonlines(self):
self.assertEqual(process.stdout, self.jsonlines_expect)
self.assertEqual(process.stderr, '')
+ @force_not_colorized
+ def test_jsonlines_from_file(self):
+ jsonl = pathlib.Path(__file__).parent / 'json_lines.jsonl'
+ args = sys.executable, '-m', self.module, '--json-lines', jsonl
+ process = subprocess.run(args, capture_output=True, text=True, check=True)
+ self.assertEqual(process.stdout, self.jsonlines_expect)
+ self.assertEqual(process.stderr, '')
+
def test_help_flag(self):
rc, out, err = assert_python_ok('-m', self.module, '-h',
PYTHON_COLORS='0')
diff --git a/Misc/NEWS.d/next/Library/2025-04-17-15-26-35.gh-issue-132631.IDFZfb.rst b/Misc/NEWS.d/next/Library/2025-04-17-15-26-35.gh-issue-132631.IDFZfb.rst
new file mode 100644
index 000000000000000..9cc1d5a389c0854
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-17-15-26-35.gh-issue-132631.IDFZfb.rst
@@ -0,0 +1,2 @@
+Fix "I/O operation on closed file" when parsing JSON Lines file with
+:mod:`JSON CLI `.
From 26c6e3d7e381578e5e39cd6e800743e48c8c8697 Mon Sep 17 00:00:00 2001
From: Sam Gross
Date: Thu, 23 Apr 2026 15:12:19 -0400
Subject: [PATCH 076/129] [3.14] gh-113956: Make intern_common thread-safe in
free-threaded build (gh-148886) (#148927)
Avoid racing with the owning thread's refcount operations when
immortalizing an interned string: if we don't own it and its refcount
isn't merged, intern a copy we own instead. Use atomic stores in
_Py_SetImmortalUntracked so concurrent atomic reads are race-free.
(cherry picked from commit 4629c2215a9a4b3d1ec4a306cd4dd7d11dcfebb4)
---
Lib/test/test_free_threading/test_str.py | 22 +++++++-
...-04-22-14-55-18.gh-issue-113956.0VEXd6.rst | 4 ++
Objects/object.c | 6 +-
Objects/unicodeobject.c | 56 ++++++++++++++++---
4 files changed, 77 insertions(+), 11 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-22-14-55-18.gh-issue-113956.0VEXd6.rst
diff --git a/Lib/test/test_free_threading/test_str.py b/Lib/test/test_free_threading/test_str.py
index 9a1ce3620ac4b2e..11e04009956db1d 100644
--- a/Lib/test/test_free_threading/test_str.py
+++ b/Lib/test/test_free_threading/test_str.py
@@ -1,7 +1,9 @@
+import sys
+import threading
import unittest
from itertools import cycle
-from threading import Event, Thread
+from threading import Barrier, Event, Thread
from unittest import TestCase
from test.support import threading_helper
@@ -69,6 +71,24 @@ def reader_func():
for reader in readers:
reader.join()
+ def test_intern_unowned_string(self):
+ # Test interning strings owned by various threads.
+ strings = [f"intern_race_owner_{i}" for i in range(50)]
+
+ NUM_THREADS = 5
+ b = Barrier(NUM_THREADS)
+
+ def interner():
+ tid = threading.get_ident()
+ for i in range(20):
+ strings.append(f"intern_{tid}_{i}")
+ b.wait()
+ for s in strings:
+ r = sys.intern(s)
+ self.assertTrue(sys._is_interned(r))
+
+ threading_helper.run_concurrently(interner, nthreads=NUM_THREADS)
+
def test_maketrans_dict_concurrent_modification(self):
for _ in range(5):
d = {2000: 'a'}
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-22-14-55-18.gh-issue-113956.0VEXd6.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-22-14-55-18.gh-issue-113956.0VEXd6.rst
new file mode 100644
index 000000000000000..54c04bbc28d4163
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-22-14-55-18.gh-issue-113956.0VEXd6.rst
@@ -0,0 +1,4 @@
+Fix a data race in :func:`sys.intern` in the free-threaded build when
+interning a string owned by another thread. An interned copy owned by the
+current thread is used instead when it is not safe to immortalize the
+original.
diff --git a/Objects/object.c b/Objects/object.c
index 6a17f71886ed96d..fa7351bb5173fff 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -2685,9 +2685,9 @@ _Py_SetImmortalUntracked(PyObject *op)
return;
}
#ifdef Py_GIL_DISABLED
- op->ob_tid = _Py_UNOWNED_TID;
- op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
- op->ob_ref_shared = 0;
+ _Py_atomic_store_uintptr_relaxed(&op->ob_tid, _Py_UNOWNED_TID);
+ _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, _Py_IMMORTAL_REFCNT_LOCAL);
+ _Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, 0);
_Py_atomic_or_uint8(&op->ob_gc_bits, _PyGC_BITS_DEFERRED);
#elif SIZEOF_VOID_P > 4
op->ob_flags = _Py_IMMORTAL_FLAGS;
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 68c40f21a12f619..bf41aabb7d5ba9e 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -676,6 +676,14 @@ _PyUnicode_CheckConsistency(PyObject *op, int check_content)
{
#define CHECK(expr) \
do { if (!(expr)) { _PyObject_ASSERT_FAILED_MSG(op, Py_STRINGIFY(expr)); } } while (0)
+#ifdef Py_GIL_DISABLED
+# define CHECK_IF_GIL(expr) (void)(expr)
+# define CHECK_IF_FT(expr) CHECK(expr)
+#else
+# define CHECK_IF_GIL(expr) CHECK(expr)
+# define CHECK_IF_FT(expr) (void)(expr)
+#endif
+
assert(op != NULL);
CHECK(PyUnicode_Check(op));
@@ -756,11 +764,9 @@ _PyUnicode_CheckConsistency(PyObject *op, int check_content)
/* Check interning state */
#ifdef Py_DEBUG
- // Note that we do not check `_Py_IsImmortal(op)`, since stable ABI
- // extensions can make immortal strings mortal (but with a high enough
- // refcount).
- // The other way is extremely unlikely (worth a potential failed assertion
- // in a debug build), so we do check `!_Py_IsImmortal(op)`.
+ // Note that we do not check `_Py_IsImmortal(op)` in the GIL-enabled build
+ // since stable ABI extensions can make immortal strings mortal (but with a
+ // high enough refcount).
switch (PyUnicode_CHECK_INTERNED(op)) {
case SSTATE_NOT_INTERNED:
if (ascii->state.statically_allocated) {
@@ -770,18 +776,20 @@ _PyUnicode_CheckConsistency(PyObject *op, int check_content)
// are static but use SSTATE_NOT_INTERNED
}
else {
- CHECK(!_Py_IsImmortal(op));
+ CHECK_IF_GIL(!_Py_IsImmortal(op));
}
break;
case SSTATE_INTERNED_MORTAL:
CHECK(!ascii->state.statically_allocated);
- CHECK(!_Py_IsImmortal(op));
+ CHECK_IF_GIL(!_Py_IsImmortal(op));
break;
case SSTATE_INTERNED_IMMORTAL:
CHECK(!ascii->state.statically_allocated);
+ CHECK_IF_FT(_Py_IsImmortal(op));
break;
case SSTATE_INTERNED_IMMORTAL_STATIC:
CHECK(ascii->state.statically_allocated);
+ CHECK_IF_FT(_Py_IsImmortal(op));
break;
default:
Py_UNREACHABLE();
@@ -15961,6 +15969,18 @@ immortalize_interned(PyObject *s)
FT_ATOMIC_STORE_UINT8(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_IMMORTAL);
}
+#ifdef Py_GIL_DISABLED
+static bool
+can_immortalize_safely(PyObject *s)
+{
+ if (_Py_IsOwnedByCurrentThread(s) || _Py_IsImmortal(s)) {
+ return true;
+ }
+ Py_ssize_t shared = _Py_atomic_load_ssize(&s->ob_ref_shared);
+ return _Py_REF_IS_MERGED(shared);
+}
+#endif
+
static /* non-null */ PyObject*
intern_common(PyInterpreterState *interp, PyObject *s /* stolen */,
bool immortalize)
@@ -15989,11 +16009,16 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */,
// no, go on
break;
case SSTATE_INTERNED_MORTAL:
+#ifndef Py_GIL_DISABLED
// yes but we might need to make it immortal
if (immortalize) {
immortalize_interned(s);
}
return s;
+#else
+ // not fully interned yet; fall through to the locking path
+ break;
+#endif
default:
// all done
return s;
@@ -16057,6 +16082,23 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */,
Py_DECREF(r);
}
#endif
+
+#ifdef Py_GIL_DISABLED
+ // Immortalization writes to the refcount fields non-atomically. That
+ // races with Py_INCREF / Py_DECREF on the thread that owns `s`. If we
+ // don't own it (and its refcount hasn't been merged), intern a copy
+ // we own instead.
+ if (!can_immortalize_safely(s)) {
+ PyObject *copy = _PyUnicode_Copy(s);
+ if (copy == NULL) {
+ PyErr_Clear();
+ return s;
+ }
+ Py_DECREF(s);
+ s = copy;
+ }
+#endif
+
LOCK_INTERNED(interp);
PyObject *t;
{
From 3034c8fa60b6162a52cb0853879b5f812a23c0ef Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Fri, 24 Apr 2026 07:52:35 +0200
Subject: [PATCH 077/129] [3.14] Additional itertool recipes for running
statistics (gh-148879) (gh-148949)
---
Doc/library/itertools.rst | 75 ++++++++++++++++++++++++++++++++++-----
1 file changed, 66 insertions(+), 9 deletions(-)
diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst
index 1be8406bbdf0c14..8bfe5ac31e8990d 100644
--- a/Doc/library/itertools.rst
+++ b/Doc/library/itertools.rst
@@ -836,6 +836,7 @@ and :term:`generators ` which incur interpreter overhead.
from collections import Counter, deque
from contextlib import suppress
from functools import reduce
+ from heapq import heappush, heappushpop, heappush_max, heappushpop_max
from math import comb, isqrt, prod, sumprod
from operator import getitem, is_not, itemgetter, mul, neg, truediv
@@ -851,11 +852,6 @@ and :term:`generators ` which incur interpreter overhead.
# prepend(1, [2, 3, 4]) → 1 2 3 4
return chain([value], iterable)
- def running_mean(iterable):
- "Yield the average of all values seen so far."
- # running_mean([8.5, 9.5, 7.5, 6.5]) → 8.5 9.0 8.5 8.0
- return map(truediv, accumulate(iterable), count(1))
-
def repeatfunc(function, times=None, *args):
"Repeat calls to a function with specified arguments."
if times is None:
@@ -1153,6 +1149,49 @@ and :term:`generators ` which incur interpreter overhead.
return n
+ # ==== Running statistics ====
+
+ def running_mean(iterable):
+ "Average of values seen so far."
+ # running_mean([37, 33, 38, 28]) → 37 35 36 34
+ return map(truediv, accumulate(iterable), count(1))
+
+ def running_min(iterable):
+ "Smallest of values seen so far."
+ # running_min([37, 33, 38, 28]) → 37 33 33 28
+ return accumulate(iterable, func=min)
+
+ def running_max(iterable):
+ "Largest of values seen so far."
+ # running_max([37, 33, 38, 28]) → 37 37 38 38
+ return accumulate(iterable, func=max)
+
+ def running_median(iterable):
+ "Median of values seen so far."
+ # running_median([37, 33, 38, 28]) → 37 35 37 35
+ read = iter(iterable).__next__
+ lo = [] # max-heap
+ hi = [] # min-heap the same size as or one smaller than lo
+ with suppress(StopIteration):
+ while True:
+ heappush_max(lo, heappushpop(hi, read()))
+ yield lo[0]
+ heappush(hi, heappushpop_max(lo, read()))
+ yield (lo[0] + hi[0]) / 2
+
+ def running_statistics(iterable):
+ "Aggregate statistics for values seen so far."
+ # Generate tuples: (size, minimum, median, maximum, mean)
+ t0, t1, t2, t3 = tee(iterable, 4)
+ return zip(
+ count(1),
+ running_min(t0),
+ running_median(t1),
+ running_max(t2),
+ running_mean(t3),
+ )
+
+
.. doctest::
:hide:
@@ -1229,10 +1268,6 @@ and :term:`generators ` which incur interpreter overhead.
[(0, 'a'), (1, 'b'), (2, 'c')]
- >>> list(running_mean([8.5, 9.5, 7.5, 6.5]))
- [8.5, 9.0, 8.5, 8.0]
-
-
>>> for _ in loops(5):
... print('hi')
...
@@ -1792,6 +1827,28 @@ and :term:`generators ` which incur interpreter overhead.
True
+ >>> list(running_mean([8.5, 9.5, 7.5, 6.5]))
+ [8.5, 9.0, 8.5, 8.0]
+ >>> list(running_mean([37, 33, 38, 28]))
+ [37.0, 35.0, 36.0, 34.0]
+
+
+ >>> list(running_min([37, 33, 38, 28]))
+ [37, 33, 33, 28]
+
+
+ >>> list(running_max([37, 33, 38, 28]))
+ [37, 37, 38, 38]
+
+
+ >>> list(running_median([37, 33, 38, 28]))
+ [37, 35.0, 37, 35.0]
+
+
+ >>> list(running_statistics([37, 33, 38, 28]))
+ [(1, 37, 37, 37, 37.0), (2, 33, 35.0, 37, 35.0), (3, 33, 37, 38, 36.0), (4, 28, 35.0, 38, 34.0)]
+
+
.. testcode::
:hide:
From 89f44ac4229e41efc6cbe36f82f66f2f8f58ba2f Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Fri, 24 Apr 2026 22:38:15 +0200
Subject: [PATCH 078/129] [3.14] gh-148735: Fix a UAF in `Element.findtext()`
(GH-148738) (#148916)
(cherry picked from commit 0469e6d38dcb3ff904690028cb3a25155bdcedae)
Co-authored-by: Stan Ulbrych
---
Lib/test/test_xml_etree.py | 10 +++++++++
...-04-18-21-39-15.gh-issue-148735.siw6DG.rst | 3 +++
Modules/_elementtree.c | 22 ++++++++-----------
3 files changed, 22 insertions(+), 13 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index b207dbe68be94cf..2f66c6b80d1436d 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -3179,6 +3179,16 @@ def test_findtext_with_mutating(self):
e.extend([ET.Element('bar')])
e.findtext(cls(e, 'x'))
+ def test_findtext_with_mutating_non_none_text(self):
+ for cls in [MutationDeleteElementPath, MutationClearElementPath]:
+ with self.subTest(cls):
+ e = ET.Element('foo')
+ child = ET.Element('bar')
+ child.text = str(object())
+ e.append(child)
+ del child
+ repr(e.findtext(cls(e, 'x')))
+
def test_findtext_with_error(self):
e = ET.Element('foo')
e.extend([ET.Element('bar')])
diff --git a/Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst b/Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst
new file mode 100644
index 000000000000000..db5e94c0ccac501
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst
@@ -0,0 +1,3 @@
+:mod:`xml.etree.ElementTree`: Fix a use-after-free in
+:meth:`Element.findtext ` when the
+element tree is mutated concurrently during the search.
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 496864175725e63..7ca03f1561b51b1 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -564,7 +564,7 @@ element_get_attrib(ElementObject* self)
LOCAL(PyObject*)
element_get_text(ElementObject* self)
{
- /* return borrowed reference to text attribute */
+ /* return new reference to text attribute */
PyObject *res = self->text;
@@ -579,13 +579,13 @@ element_get_text(ElementObject* self)
}
}
- return res;
+ return Py_NewRef(res);
}
LOCAL(PyObject*)
element_get_tail(ElementObject* self)
{
- /* return borrowed reference to text attribute */
+ /* return new reference to tail attribute */
PyObject *res = self->tail;
@@ -600,7 +600,7 @@ element_get_tail(ElementObject* self)
}
}
- return res;
+ return Py_NewRef(res);
}
static PyObject*
@@ -1350,9 +1350,9 @@ _elementtree_Element_findtext_impl(ElementObject *self, PyTypeObject *cls,
PyObject *text = element_get_text((ElementObject *)item);
Py_DECREF(item);
if (text == Py_None) {
+ Py_DECREF(text);
return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
}
- Py_XINCREF(text);
return text;
}
Py_DECREF(item);
@@ -2056,16 +2056,14 @@ static PyObject*
element_text_getter(PyObject *op, void *closure)
{
ElementObject *self = _Element_CAST(op);
- PyObject *res = element_get_text(self);
- return Py_XNewRef(res);
+ return element_get_text(self);
}
static PyObject*
element_tail_getter(PyObject *op, void *closure)
{
ElementObject *self = _Element_CAST(op);
- PyObject *res = element_get_tail(self);
- return Py_XNewRef(res);
+ return element_get_tail(self);
}
static PyObject*
@@ -2308,16 +2306,14 @@ elementiter_next(PyObject *op)
continue;
gettext:
+ Py_DECREF(elem);
if (!text) {
- Py_DECREF(elem);
return NULL;
}
if (text == Py_None) {
- Py_DECREF(elem);
+ Py_DECREF(text);
}
else {
- Py_INCREF(text);
- Py_DECREF(elem);
rc = PyObject_IsTrue(text);
if (rc > 0)
return text;
From 8acb98a1c269cf34a421faed1ee3e2c78e98e2bd Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sat, 25 Apr 2026 14:53:42 +0200
Subject: [PATCH 079/129] [3.14] gh-148973: fix segfault on mismatch between
consts size and oparg in compiler (GH-148974) (#148980)
gh-148973: fix segfault on mismatch between consts size and oparg in compiler (GH-148974)
(cherry picked from commit c650b51c32f92563f3319bb25c64ca2d2dc05ec0)
Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
---
Lib/test/test_peepholer.py | 48 ++++++++++++++++++++++++++++
Modules/_testinternalcapi.c | 11 +++++--
Modules/clinic/_testinternalcapi.c.h | 13 ++++++--
Python/compile.c | 14 +++++++-
Python/flowgraph.c | 15 +++++++++
5 files changed, 95 insertions(+), 6 deletions(-)
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 3a232e5ff085fe4..d06b9a4b6efb5fd 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -1,3 +1,4 @@
+import ast
import dis
import gc
from itertools import combinations, product
@@ -1119,6 +1120,53 @@ def trace(frame, event, arg):
class DirectCfgOptimizerTests(CfgOptimizationTestCase):
+ def test_optimize_cfg_const_index_out_of_range(self):
+ insts = [
+ ('LOAD_CONST', 2, 0),
+ ('RETURN_VALUE', None, 0),
+ ]
+ seq = self.seq_from_insts(insts)
+ with self.assertRaisesRegex(ValueError, "out of range"):
+ _testinternalcapi.optimize_cfg(seq, [0, 1], 0)
+
+ def test_optimize_cfg_consts_must_be_list(self):
+ insts = [
+ ('LOAD_CONST', 0, 0),
+ ('RETURN_VALUE', None, 0),
+ ]
+ seq = self.seq_from_insts(insts)
+ with self.assertRaisesRegex(TypeError, "consts must be a list"):
+ _testinternalcapi.optimize_cfg(seq, (0,), 0)
+
+ def test_compiler_codegen_metadata_consts_roundtrips_optimize_cfg(self):
+ tree = ast.parse("x = (1, 2)", mode="exec", optimize=1)
+ insts, meta = _testinternalcapi.compiler_codegen(tree, "", 0)
+ consts = meta["consts"]
+ self.assertIsInstance(consts, list)
+ _testinternalcapi.optimize_cfg(insts, consts, 0)
+
+ def test_compiler_codegen_consts_include_none_required_for_implicit_return(self):
+ # Module "pass" only needs the const table entry for None once
+ # _PyCodegen_AddReturnAtEnd runs. If metadata["consts"] were taken
+ # before that, the list would not match LOAD_CONST opargs (here: 0
+ # for None), and optimize_cfg would read out of range.
+ tree = ast.parse("pass", mode="exec", optimize=1)
+ insts, meta = _testinternalcapi.compiler_codegen(tree, "", 0)
+ consts = meta["consts"]
+ self.assertEqual(consts, [None])
+
+ load_const = opcode.opmap["LOAD_CONST"]
+ self.assertEqual(
+ [t[1] for t in insts.get_instructions() if t[0] == load_const],
+ [0],
+ )
+
+ # As if consts were snapshotted before AddReturnAtEnd: still LOAD_CONST 0, no row.
+ with self.assertRaisesRegex(ValueError, "out of range"):
+ _testinternalcapi.optimize_cfg(insts, [], 0)
+
+ _testinternalcapi.optimize_cfg(insts, list(consts), 0)
+
def cfg_optimization_test(self, insts, expected_insts,
consts=None, expected_consts=None,
nlocals=0):
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index b2b4e4ce1d2bd93..158c26d442ed133 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -732,13 +732,17 @@ _testinternalcapi.compiler_codegen -> object
compile_mode: int = 0
Apply compiler code generation to an AST.
+
+Return (instruction_sequence, metadata). metadata maps "argcount",
+"posonlyargcount", "kwonlyargcount" to ints and "consts" to the list of
+constants in LOAD_CONST index order (for use with optimize_cfg).
[clinic start generated code]*/
static PyObject *
_testinternalcapi_compiler_codegen_impl(PyObject *module, PyObject *ast,
PyObject *filename, int optimize,
int compile_mode)
-/*[clinic end generated code: output=40a68f6e13951cc8 input=a0e00784f1517cd7]*/
+/*[clinic end generated code: output=40a68f6e13951cc8 input=e0c65e5c80efe30e]*/
{
PyCompilerFlags *flags = NULL;
return _PyCompile_CodeGen(ast, filename, flags, optimize, compile_mode);
@@ -754,12 +758,15 @@ _testinternalcapi.optimize_cfg -> object
nlocals: int
Apply compiler optimizations to an instruction list.
+
+consts must be a list aligned with LOAD_CONST opargs (the "consts" entry
+from the metadata dict returned by compiler_codegen for the same unit).
[clinic start generated code]*/
static PyObject *
_testinternalcapi_optimize_cfg_impl(PyObject *module, PyObject *instructions,
PyObject *consts, int nlocals)
-/*[clinic end generated code: output=57c53c3a3dfd1df0 input=6a96d1926d58d7e5]*/
+/*[clinic end generated code: output=57c53c3a3dfd1df0 input=905c3d935e063b27]*/
{
return _PyCompile_OptimizeCfg(instructions, consts, nlocals);
}
diff --git a/Modules/clinic/_testinternalcapi.c.h b/Modules/clinic/_testinternalcapi.c.h
index 21f4ee3201e5bf3..85edc6fbb5802f0 100644
--- a/Modules/clinic/_testinternalcapi.c.h
+++ b/Modules/clinic/_testinternalcapi.c.h
@@ -92,7 +92,11 @@ PyDoc_STRVAR(_testinternalcapi_compiler_codegen__doc__,
"compiler_codegen($module, /, ast, filename, optimize, compile_mode=0)\n"
"--\n"
"\n"
-"Apply compiler code generation to an AST.");
+"Apply compiler code generation to an AST.\n"
+"\n"
+"Return (instruction_sequence, metadata). metadata maps \"argcount\",\n"
+"\"posonlyargcount\", \"kwonlyargcount\" to ints and \"consts\" to the list of\n"
+"constants in LOAD_CONST index order (for use with optimize_cfg).");
#define _TESTINTERNALCAPI_COMPILER_CODEGEN_METHODDEF \
{"compiler_codegen", _PyCFunction_CAST(_testinternalcapi_compiler_codegen), METH_FASTCALL|METH_KEYWORDS, _testinternalcapi_compiler_codegen__doc__},
@@ -169,7 +173,10 @@ PyDoc_STRVAR(_testinternalcapi_optimize_cfg__doc__,
"optimize_cfg($module, /, instructions, consts, nlocals)\n"
"--\n"
"\n"
-"Apply compiler optimizations to an instruction list.");
+"Apply compiler optimizations to an instruction list.\n"
+"\n"
+"consts must be a list aligned with LOAD_CONST opargs (the \"consts\" entry\n"
+"from the metadata dict returned by compiler_codegen for the same unit).");
#define _TESTINTERNALCAPI_OPTIMIZE_CFG_METHODDEF \
{"optimize_cfg", _PyCFunction_CAST(_testinternalcapi_optimize_cfg), METH_FASTCALL|METH_KEYWORDS, _testinternalcapi_optimize_cfg__doc__},
@@ -392,4 +399,4 @@ get_next_dict_keys_version(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return get_next_dict_keys_version_impl(module);
}
-/*[clinic end generated code: output=fbd8b7e0cae8bac7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=ecb5d7ac85b153fa input=a9049054013a1b77]*/
diff --git a/Python/compile.c b/Python/compile.c
index a0430b07d476d39..d79e4b44c2c02cb 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -1610,6 +1610,7 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags,
{
PyObject *res = NULL;
PyObject *metadata = NULL;
+ PyObject *consts_list = NULL;
if (!PyAST_Check(ast)) {
PyErr_SetString(PyExc_TypeError, "expected an AST");
@@ -1664,12 +1665,23 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags,
}
if (_PyInstructionSequence_ApplyLabelMap(_PyCompile_InstrSequence(c)) < 0) {
- return NULL;
+ goto finally;
+ }
+
+ /* After AddReturnAtEnd: co_consts indices match the final instruction stream. */
+ consts_list = consts_dict_keys_inorder(umd->u_consts);
+ if (consts_list == NULL) {
+ goto finally;
+ }
+ if (PyDict_SetItemString(metadata, "consts", consts_list) < 0) {
+ goto finally;
}
+
/* Allocate a copy of the instruction sequence on the heap */
res = PyTuple_Pack(2, _PyCompile_InstrSequence(c), metadata);
finally:
+ Py_XDECREF(consts_list);
Py_XDECREF(metadata);
_PyCompile_ExitScope(c);
compiler_free(c);
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index 87e90ddeef91d58..5b57380800d03d1 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -1295,6 +1295,14 @@ get_const_value(int opcode, int oparg, PyObject *co_consts)
PyObject *constant = NULL;
assert(loads_const(opcode));
if (opcode == LOAD_CONST) {
+ assert(PyList_Check(co_consts));
+ Py_ssize_t n = PyList_GET_SIZE(co_consts);
+ if (oparg < 0 || oparg >= n) {
+ PyErr_Format(PyExc_ValueError,
+ "LOAD_CONST index %d is out of range for consts (len=%zd)",
+ oparg, n);
+ return NULL;
+ }
constant = PyList_GET_ITEM(co_consts, oparg);
}
if (opcode == LOAD_SMALL_INT) {
@@ -2153,6 +2161,9 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject *
cfg_instr *inst = &bb->b_instr[i];
if (inst->i_opcode == LOAD_CONST) {
PyObject *constant = get_const_value(inst->i_opcode, inst->i_oparg, consts);
+ if (constant == NULL) {
+ return ERROR;
+ }
int res = maybe_instr_make_load_smallint(inst, constant, consts, const_cache);
Py_DECREF(constant);
if (res < 0) {
@@ -4073,6 +4084,10 @@ _PyCompile_OptimizeCfg(PyObject *seq, PyObject *consts, int nlocals)
PyErr_SetString(PyExc_ValueError, "expected an instruction sequence");
return NULL;
}
+ if (!PyList_Check(consts)) {
+ PyErr_SetString(PyExc_TypeError, "consts must be a list");
+ return NULL;
+ }
PyObject *const_cache = PyDict_New();
if (const_cache == NULL) {
return NULL;
From 15e2b64cd8bfaf0d5127584a88bb6b929f8dcc19 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sat, 25 Apr 2026 17:57:33 +0200
Subject: [PATCH 080/129] [3.14] gh-148947: dataclasses: fix error on empty
__class__ cell (GH-148948) (#148995)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
gh-148947: dataclasses: fix error on empty __class__ cell (GH-148948)
Also add a test demonstrating the need for the existing "is oldcls" check.
(cherry picked from commit 6d7bbee1d5714a345dca5a7e4089de3c2fc0fb59)
Co-authored-by: Jelle Zijlstra
Co-authored-by: Bartosz Sławecki
---
Lib/dataclasses.py | 14 ++++--
Lib/test/test_dataclasses/__init__.py | 46 +++++++++++++++++++
...-04-23-21-47-49.gh-issue-148947.W4V2lG.rst | 2 +
3 files changed, 59 insertions(+), 3 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 6f0539b740a83fd..f9c1113b1e367f4 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -1292,10 +1292,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
# This function doesn't reference __class__, so nothing to do.
return False
# Fix the cell to point to the new class, if it's already pointing
- # at the old class. I'm not convinced that the "is oldcls" test
- # is needed, but other than performance can't hurt.
+ # at the old class.
closure = f.__closure__[idx]
- if closure.cell_contents is oldcls:
+
+ try:
+ contents = closure.cell_contents
+ except ValueError:
+ # Cell is empty
+ return False
+
+ # This check makes it so we avoid updating an incorrect cell if the
+ # class body contains a function that was defined in a different class.
+ if contents is oldcls:
closure.cell_contents = newcls
return True
return False
diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py
index 10e040976ae83b3..fed32260c792707 100644
--- a/Lib/test/test_dataclasses/__init__.py
+++ b/Lib/test/test_dataclasses/__init__.py
@@ -5361,5 +5361,51 @@ def cls(self):
# one will be keeping a reference to the underlying class A.
self.assertIs(A().cls(), B)
+ def test_empty_class_cell(self):
+ # gh-148947: Make sure that we explicitly handle the empty class cell.
+ def maker():
+ if False:
+ __class__ = 42
+
+ def method(self):
+ return __class__
+ return method
+
+ from dataclasses import dataclass
+
+ @dataclass(slots=True)
+ class X:
+ a: int
+
+ meth = maker()
+
+ with self.assertRaisesRegex(NameError, '__class__'):
+ X(1).meth()
+
+ def test_class_cell_from_other_class(self):
+ # This test fails without the "is oldcls" check in
+ # _update_func_cell_for__class__.
+ class Base:
+ def meth(self):
+ return "Base"
+
+ class Child(Base):
+ def meth(self):
+ return super().meth() + " Child"
+
+ @dataclass(slots=True)
+ class DC(Child):
+ a: int
+
+ meth = Child.meth
+
+ closure = DC.meth.__closure__
+ self.assertEqual(len(closure), 1)
+ self.assertIs(closure[0].cell_contents, Child)
+
+ self.assertEqual(DC(1).meth(), "Base Child")
+
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst b/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst
new file mode 100644
index 000000000000000..f9783266f5cc42c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst
@@ -0,0 +1,2 @@
+Fix crash in :deco:`dataclasses.dataclass` with ``slots=True`` that occurred
+when a function found within the class had an empty ``__class__`` cell.
From 5770df43dc6d8d23b66b843ee708e4b239af35a8 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sun, 26 Apr 2026 06:29:47 +0200
Subject: [PATCH 081/129] [3.14] gh-141473: Speed up subprocess
test_communicate_timeout_large_input long tail (GH-149003) (#149004)
gh-141473: Speed up subprocess test_communicate_timeout_large_input long tail (GH-149003)
gh-141473: Speed up test_communicate_timeout_large_input
Replace the slow reader's 30s sleep with a parent-driven wake over a
loopback socket so post-timeout communicate() doesn't block waiting
for the child to wake on its own. Worst-case runtime: ~30s -> <1s.
(cherry picked from commit e1384cfd25b4fba5e0f8f3e6b536930e2e6cf5cf)
Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com>
---
Lib/test/test_subprocess.py | 56 +++++++++++++++++++++++++++++++------
1 file changed, 48 insertions(+), 8 deletions(-)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 806a1e3fa303eb5..7e04934c317a91d 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -22,6 +22,7 @@
import sysconfig
import select
import shutil
+import socket
import threading
import gc
import textwrap
@@ -1044,19 +1045,49 @@ def test_communicate_timeout_large_input(self):
# On Windows, stdin writing must also honor the timeout rather than
# blocking indefinitely when the pipe buffer fills.
- # Input larger than typical pipe buffer (4-64KB on Windows)
- input_data = b"x" * (128 * 1024)
+ input_data = b"x" * (128 * 1024) # > typical pipe buffer
+
+ # Cross-platform wake mechanism: the slow reader connects to a
+ # loopback TCP socket and blocks in select() on it (capped at 9s
+ # as a safety net we don't expect to hit). After phase 1 raises
+ # TimeoutExpired, the parent sends a byte to release the child so
+ # it drains stdin. A socket (rather than a raw pipe) is required
+ # because Windows select() only supports sockets, not arbitrary
+ # file descriptors.
+ server = socket.create_server(('127.0.0.1', 0), backlog=1)
+ server.settimeout(10) # bound the accept() if the child fails to start
+ port = server.getsockname()[1]
+ # The child sends one byte (low byte of its PID) first so the parent
+ # can detect the rare case of an unrelated process on the same host
+ # connecting to our ephemeral port before our child does. A single
+ # byte gives 1/256 collision odds, which is plenty for flake-prevention.
+ slow_reader = (
+ "import os, socket, sys, select; "
+ f"s = socket.create_connection(('127.0.0.1', {port}), timeout=9); "
+ "s.sendall(bytes([os.getpid() & 0xff])); "
+ "select.select([s], [], [], 9); "
+ "sys.stdout.buffer.write(sys.stdin.buffer.read())"
+ )
p = subprocess.Popen(
- [sys.executable, "-c",
- "import sys, time; "
- "time.sleep(30); " # Don't read stdin for a long time
- "sys.stdout.buffer.write(sys.stdin.buffer.read())"],
+ [sys.executable, "-c", slow_reader],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
+ conn = None
try:
+ conn, _ = server.accept()
+ server.close()
+ server = None
+
+ conn.settimeout(5)
+ peer_byte = conn.recv(1)
+ conn.settimeout(None)
+ self.assertEqual(peer_byte, bytes([p.pid & 0xff]),
+ f"loopback handshake byte {peer_byte!r} != "
+ f"low byte of child PID {p.pid} ({p.pid & 0xff:#x})")
+
timeout = 0.2
start = time.monotonic()
try:
@@ -1065,7 +1096,7 @@ def test_communicate_timeout_large_input(self):
elapsed = time.monotonic() - start
self.fail(
f"TimeoutExpired not raised. communicate() completed in "
- f"{elapsed:.2f}s, but subprocess sleeps for 30s. "
+ f"{elapsed:.2f}s, but slow reader stalls for up to 9s. "
"Stdin writing blocked without enforcing timeout.")
except subprocess.TimeoutExpired:
elapsed = time.monotonic() - start
@@ -1073,11 +1104,16 @@ def test_communicate_timeout_large_input(self):
# Timeout should occur close to the specified timeout value,
# not after waiting for the subprocess to finish sleeping.
# Allow generous margin for slow CI, but must be well under
- # the subprocess sleep time.
+ # the slow-reader's stall cap.
self.assertLess(elapsed, 5.0,
f"TimeoutExpired raised after {elapsed:.2f}s; expected ~{timeout}s. "
"Stdin writing blocked without checking timeout.")
+ # Release the slow reader so it stops blocking and drains stdin.
+ conn.sendall(b'go')
+ conn.close()
+ conn = None
+
# After timeout, continue communication. The remaining input
# should be sent and we should receive all data back.
stdout, stderr = p.communicate()
@@ -1087,6 +1123,10 @@ def test_communicate_timeout_large_input(self):
f"Expected {len(input_data)} bytes output but got {len(stdout)}")
self.assertEqual(stdout, input_data)
finally:
+ if conn is not None:
+ conn.close()
+ if server is not None:
+ server.close()
p.kill()
p.wait()
From 78c5e54b4fd5798d8b74b901c34539bb09a5a064 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Sun, 26 Apr 2026 17:45:38 +0200
Subject: [PATCH 082/129] =?UTF-8?q?[3.14]=20gh-146455:=20Fix=20O(N=C2=B2)?=
=?UTF-8?q?=20in=20add=5Fconst()=20after=20constant=20folding=20moved=20to?=
=?UTF-8?q?=20CFG=20(GH-146456)=20(#149011)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
gh-146455: Fix O(N²) in add_const() after constant folding moved to CFG (GH-146456)
The add_const() function in flowgraph.c uses a linear search over the
consts list to find the index of a constant. After gh-126835 moved
constant folding from the AST optimizer to the CFG optimizer, this
function is now called N times for N inner tuple elements during
fold_tuple_of_constants(), resulting in O(N²) total time.
Fix by maintaining an auxiliary _Py_hashtable_t that maps object
pointers to their indices in the consts list, providing O(1) lookup.
For a file with 100,000 constant 2-tuples:
- Before: 10.38s (add_const occupies 83.76% of CPU time)
- After: 1.48s
(cherry picked from commit 5d416324c56cd6f262fa123f41b97b48631bea79)
Co-authored-by: zSirius <107359899+zSirius@users.noreply.github.com>
---
...3-26-08-49-35.gh-issue-146455.f54083a9.rst | 1 +
Python/flowgraph.c | 131 ++++++++++++------
2 files changed, 89 insertions(+), 43 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst
new file mode 100644
index 000000000000000..4d7537f2529da6b
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst
@@ -0,0 +1 @@
+Fix O(N²) compile-time regression in constant folding after it was moved from AST to CFG optimizer.
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index 5b57380800d03d1..eff2fc90d3c02d6 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -6,6 +6,7 @@
#include "pycore_intrinsics.h"
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
#include "pycore_long.h" // _PY_IS_SMALL_INT()
+#include "pycore_hashtable.h" // _Py_hashtable_t
#include "pycore_opcode_utils.h"
#include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc
@@ -1319,30 +1320,38 @@ get_const_value(int opcode, int oparg, PyObject *co_consts)
// Steals a reference to newconst.
static int
-add_const(PyObject *newconst, PyObject *consts, PyObject *const_cache)
+add_const(PyObject *newconst, PyObject *consts, PyObject *const_cache,
+ _Py_hashtable_t *consts_index)
{
if (_PyCompile_ConstCacheMergeOne(const_cache, &newconst) < 0) {
Py_DECREF(newconst);
return -1;
}
- Py_ssize_t index;
- for (index = 0; index < PyList_GET_SIZE(consts); index++) {
- if (PyList_GET_ITEM(consts, index) == newconst) {
- break;
- }
+ _Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(consts_index, (void *)newconst);
+ if (entry != NULL) {
+ Py_DECREF(newconst);
+ return (int)(uintptr_t)entry->value;
}
- if (index == PyList_GET_SIZE(consts)) {
- if ((size_t)index >= (size_t)INT_MAX - 1) {
- PyErr_SetString(PyExc_OverflowError, "too many constants");
- Py_DECREF(newconst);
- return -1;
- }
- if (PyList_Append(consts, newconst)) {
- Py_DECREF(newconst);
- return -1;
- }
+
+ Py_ssize_t index = PyList_GET_SIZE(consts);
+ if ((size_t)index >= (size_t)INT_MAX - 1) {
+ PyErr_SetString(PyExc_OverflowError, "too many constants");
+ Py_DECREF(newconst);
+ return -1;
+ }
+ if (PyList_Append(consts, newconst)) {
+ Py_DECREF(newconst);
+ return -1;
+ }
+
+ if (_Py_hashtable_set(consts_index, (void *)newconst, (void *)(uintptr_t)index) < 0) {
+ PyList_SetSlice(consts, index, index + 1, NULL);
+ Py_DECREF(newconst);
+ PyErr_NoMemory();
+ return -1;
}
+
Py_DECREF(newconst);
return (int)index;
}
@@ -1418,7 +1427,8 @@ maybe_instr_make_load_smallint(cfg_instr *instr, PyObject *newconst,
/* Steals reference to "newconst" */
static int
instr_make_load_const(cfg_instr *instr, PyObject *newconst,
- PyObject *consts, PyObject *const_cache)
+ PyObject *consts, PyObject *const_cache,
+ _Py_hashtable_t *consts_index)
{
int res = maybe_instr_make_load_smallint(instr, newconst, consts, const_cache);
if (res < 0) {
@@ -1428,7 +1438,7 @@ instr_make_load_const(cfg_instr *instr, PyObject *newconst,
if (res > 0) {
return SUCCESS;
}
- int oparg = add_const(newconst, consts, const_cache);
+ int oparg = add_const(newconst, consts, const_cache, consts_index);
RETURN_IF_ERROR(oparg);
INSTR_SET_OP1(instr, LOAD_CONST, oparg);
return SUCCESS;
@@ -1441,7 +1451,8 @@ instr_make_load_const(cfg_instr *instr, PyObject *newconst,
Called with codestr pointing to the first LOAD_CONST.
*/
static int
-fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
+fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts,
+ PyObject *const_cache, _Py_hashtable_t *consts_index)
{
/* Pre-conditions */
assert(PyDict_CheckExact(const_cache));
@@ -1478,7 +1489,7 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
}
nop_out(const_instrs, seq_size);
- return instr_make_load_const(instr, const_tuple, consts, const_cache);
+ return instr_make_load_const(instr, const_tuple, consts, const_cache, consts_index);
}
/* Replace:
@@ -1496,7 +1507,8 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
*/
static int
fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
- PyObject *consts, PyObject *const_cache)
+ PyObject *consts, PyObject *const_cache,
+ _Py_hashtable_t *consts_index)
{
assert(PyDict_CheckExact(const_cache));
assert(PyList_CheckExact(consts));
@@ -1548,7 +1560,7 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
nop_out(&instr, 1);
}
assert(consts_found == 0);
- return instr_make_load_const(intrinsic, newconst, consts, const_cache);
+ return instr_make_load_const(intrinsic, newconst, consts, const_cache, consts_index);
}
if (expect_append) {
@@ -1584,7 +1596,8 @@ Optimize lists and sets for:
*/
static int
optimize_lists_and_sets(basicblock *bb, int i, int nextop,
- PyObject *consts, PyObject *const_cache)
+ PyObject *consts, PyObject *const_cache,
+ _Py_hashtable_t *consts_index)
{
assert(PyDict_CheckExact(const_cache));
assert(PyList_CheckExact(consts));
@@ -1634,7 +1647,7 @@ optimize_lists_and_sets(basicblock *bb, int i, int nextop,
Py_SETREF(const_result, frozenset);
}
- int index = add_const(const_result, consts, const_cache);
+ int index = add_const(const_result, consts, const_cache, consts_index);
RETURN_IF_ERROR(index);
nop_out(const_instrs, seq_size);
@@ -1831,7 +1844,8 @@ eval_const_binop(PyObject *left, int op, PyObject *right)
}
static int
-fold_const_binop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
+fold_const_binop(basicblock *bb, int i, PyObject *consts,
+ PyObject *const_cache, _Py_hashtable_t *consts_index)
{
#define BINOP_OPERAND_COUNT 2
assert(PyDict_CheckExact(const_cache));
@@ -1873,7 +1887,7 @@ fold_const_binop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
}
nop_out(operands_instrs, BINOP_OPERAND_COUNT);
- return instr_make_load_const(binop, newconst, consts, const_cache);
+ return instr_make_load_const(binop, newconst, consts, const_cache, consts_index);
}
static PyObject *
@@ -1919,7 +1933,8 @@ eval_const_unaryop(PyObject *operand, int opcode, int oparg)
}
static int
-fold_const_unaryop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
+fold_const_unaryop(basicblock *bb, int i, PyObject *consts,
+ PyObject *const_cache, _Py_hashtable_t *consts_index)
{
#define UNARYOP_OPERAND_COUNT 1
assert(PyDict_CheckExact(const_cache));
@@ -1956,7 +1971,7 @@ fold_const_unaryop(basicblock *bb, int i, PyObject *consts, PyObject *const_cach
assert(PyBool_Check(newconst));
}
nop_out(&operand_instr, UNARYOP_OPERAND_COUNT);
- return instr_make_load_const(unaryop, newconst, consts, const_cache);
+ return instr_make_load_const(unaryop, newconst, consts, const_cache, consts_index);
}
#define VISITED (-1)
@@ -2151,7 +2166,8 @@ apply_static_swaps(basicblock *block, int i)
}
static int
-basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject *consts)
+basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb,
+ PyObject *consts, _Py_hashtable_t *consts_index)
{
assert(PyDict_CheckExact(const_cache));
assert(PyList_CheckExact(consts));
@@ -2269,7 +2285,7 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject *
return ERROR;
}
cnt = PyBool_FromLong(is_true);
- int index = add_const(cnt, consts, const_cache);
+ int index = add_const(cnt, consts, const_cache, consts_index);
if (index < 0) {
return ERROR;
}
@@ -2283,15 +2299,17 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject *
}
static int
-optimize_load_const(PyObject *const_cache, cfg_builder *g, PyObject *consts) {
+optimize_load_const(PyObject *const_cache, cfg_builder *g, PyObject *consts,
+ _Py_hashtable_t *consts_index) {
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
- RETURN_IF_ERROR(basicblock_optimize_load_const(const_cache, b, consts));
+ RETURN_IF_ERROR(basicblock_optimize_load_const(const_cache, b, consts, consts_index));
}
return SUCCESS;
}
static int
-optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
+optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts,
+ _Py_hashtable_t *consts_index)
{
assert(PyDict_CheckExact(const_cache));
assert(PyList_CheckExact(consts));
@@ -2331,11 +2349,11 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
continue;
}
}
- RETURN_IF_ERROR(fold_tuple_of_constants(bb, i, consts, const_cache));
+ RETURN_IF_ERROR(fold_tuple_of_constants(bb, i, consts, const_cache, consts_index));
break;
case BUILD_LIST:
case BUILD_SET:
- RETURN_IF_ERROR(optimize_lists_and_sets(bb, i, nextop, consts, const_cache));
+ RETURN_IF_ERROR(optimize_lists_and_sets(bb, i, nextop, consts, const_cache, consts_index));
break;
case POP_JUMP_IF_NOT_NONE:
case POP_JUMP_IF_NONE:
@@ -2470,7 +2488,7 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
_Py_FALLTHROUGH;
case UNARY_INVERT:
case UNARY_NEGATIVE:
- RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
+ RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache, consts_index));
break;
case CALL_INTRINSIC_1:
if (oparg == INTRINSIC_LIST_TO_TUPLE) {
@@ -2478,15 +2496,15 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
INSTR_SET_OP0(inst, NOP);
}
else {
- RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache));
+ RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache, consts_index));
}
}
else if (oparg == INTRINSIC_UNARY_POSITIVE) {
- RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
+ RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache, consts_index));
}
break;
case BINARY_OP:
- RETURN_IF_ERROR(fold_const_binop(bb, i, consts, const_cache));
+ RETURN_IF_ERROR(fold_const_binop(bb, i, consts, const_cache, consts_index));
break;
}
}
@@ -2531,16 +2549,17 @@ remove_redundant_nops_and_jumps(cfg_builder *g)
NOPs. Later those NOPs are removed.
*/
static int
-optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, int firstlineno)
+optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache,
+ _Py_hashtable_t *consts_index, int firstlineno)
{
assert(PyDict_CheckExact(const_cache));
RETURN_IF_ERROR(check_cfg(g));
RETURN_IF_ERROR(inline_small_or_no_lineno_blocks(g->g_entryblock));
RETURN_IF_ERROR(remove_unreachable(g->g_entryblock));
RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno));
- RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts));
+ RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts, consts_index));
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
- RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts));
+ RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts, consts_index));
}
RETURN_IF_ERROR(remove_redundant_nops_and_pairs(g->g_entryblock));
RETURN_IF_ERROR(remove_unreachable(g->g_entryblock));
@@ -3648,7 +3667,33 @@ _PyCfg_OptimizeCodeUnit(cfg_builder *g, PyObject *consts, PyObject *const_cache,
RETURN_IF_ERROR(label_exception_targets(g->g_entryblock));
/** Optimization **/
- RETURN_IF_ERROR(optimize_cfg(g, consts, const_cache, firstlineno));
+
+ _Py_hashtable_t *consts_index = _Py_hashtable_new(
+ _Py_hashtable_hash_ptr, _Py_hashtable_compare_direct);
+ if (consts_index == NULL) {
+ PyErr_NoMemory();
+ return ERROR;
+ }
+
+ for (Py_ssize_t i = 0; i < PyList_GET_SIZE(consts); i++) {
+ PyObject *item = PyList_GET_ITEM(consts, i);
+ if (_Py_hashtable_get_entry(consts_index, (void *)item) != NULL) {
+ continue;
+ }
+ if (_Py_hashtable_set(consts_index, (void *)item,
+ (void *)(uintptr_t)i) < 0) {
+ _Py_hashtable_destroy(consts_index);
+ PyErr_NoMemory();
+ return ERROR;
+ }
+ }
+
+ int ret = optimize_cfg(g, consts, const_cache, consts_index, firstlineno);
+
+ _Py_hashtable_destroy(consts_index);
+
+ RETURN_IF_ERROR(ret);
+
RETURN_IF_ERROR(remove_unused_consts(g->g_entryblock, consts));
RETURN_IF_ERROR(
add_checks_for_loads_of_uninitialized_variables(
From 9a7e205e463e424c0ef59085519ffda138e4e15b Mon Sep 17 00:00:00 2001
From: Sergey Miryanov
Date: Sun, 26 Apr 2026 23:12:52 +0500
Subject: [PATCH 083/129] [3.14] GH-148726: Forward-port generational GC
(#148720)
Co-authored-by: Neil Schemenauer
Co-authored-by: Sergey Miryanov
Co-authored-by: Zanie Blue
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Neil Schemenauer
---
Doc/data/python3.14.abi | 10116 ++++++++--------
Doc/library/gc.rst | 51 +-
Include/internal/pycore_gc.h | 43 +-
Include/internal/pycore_interp_structs.h | 34 +-
Include/internal/pycore_runtime_init.h | 8 +-
InternalDocs/garbage_collector.md | 191 +-
Lib/test/_test_gc_fast_cycles.py | 48 -
Lib/test/test_gc.py | 75 +-
...4-17-11-30-00.gh-issue-142516.GcGen315.rst | 1 +
Modules/_testinternalcapi.c | 3 +-
Modules/gcmodule.c | 30 +-
Python/gc.c | 1078 +-
12 files changed, 5582 insertions(+), 6096 deletions(-)
delete mode 100644 Lib/test/_test_gc_fast_cycles.py
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-17-11-30-00.gh-issue-142516.GcGen315.rst
diff --git a/Doc/data/python3.14.abi b/Doc/data/python3.14.abi
index c5630fd8dd1bd8e..7dc43dd58ce2d5f 100644
--- a/Doc/data/python3.14.abi
+++ b/Doc/data/python3.14.abi
@@ -4195,37 +4195,37 @@
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
@@ -4408,13 +4408,13 @@
-
+
-
+
@@ -5089,13 +5089,12 @@
-
-
-
-
+
+
+
-
+
@@ -5261,10 +5260,6 @@
-
-
-
-
@@ -5277,7 +5272,7 @@
-
+
@@ -5296,7 +5291,7 @@
-
+
@@ -5452,56 +5447,56 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -5510,7 +5505,7 @@
-
+
@@ -5530,11 +5525,11 @@
-
+
-
-
+
+
@@ -5552,15 +5547,15 @@
-
+
-
-
+
+
@@ -5614,7 +5609,7 @@
-
+
@@ -5671,37 +5666,37 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -5709,7 +5704,7 @@
-
+
@@ -5717,23 +5712,23 @@
-
+
-
+
-
+
-
-
+
+
-
+
-
+
@@ -5745,7 +5740,7 @@
-
+
@@ -5764,15 +5759,15 @@
-
+
-
+
-
+
@@ -5825,7 +5820,7 @@
-
+
@@ -5891,7 +5886,7 @@
-
+
@@ -5920,7 +5915,7 @@
-
+
@@ -5933,7 +5928,7 @@
-
+
@@ -5952,7 +5947,7 @@
-
+
@@ -5970,7 +5965,7 @@
-
+
@@ -5980,8 +5975,8 @@
-
-
+
+
@@ -5989,7 +5984,7 @@
-
+
@@ -6082,24 +6077,24 @@
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
@@ -6110,42 +6105,42 @@
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -6165,7 +6160,7 @@
-
+
@@ -6176,7 +6171,7 @@
-
+
@@ -6187,12 +6182,12 @@
-
+
-
+
@@ -6201,7 +6196,7 @@
-
+
@@ -6215,67 +6210,67 @@
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
@@ -6284,47 +6279,47 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
@@ -6337,25 +6332,26 @@
-
+
-
+
-
+
-
+
-
+
-
-
+
+
+
@@ -6363,7 +6359,7 @@
-
+
@@ -6382,7 +6378,7 @@
-
+
@@ -6427,7 +6423,7 @@
-
+
@@ -6435,7 +6431,7 @@
-
+
@@ -6459,7 +6455,7 @@
-
+
@@ -6479,126 +6475,126 @@
-
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
+
@@ -6610,11 +6606,11 @@
-
+
-
-
+
+
@@ -6661,7 +6657,7 @@
-
+
@@ -6685,16 +6681,16 @@
-
+
-
+
-
+
-
+
@@ -6706,12 +6702,12 @@
-
+
-
-
-
+
+
+
@@ -6719,15 +6715,15 @@
-
+
-
-
+
+
-
+
@@ -6738,7 +6734,7 @@
-
+
@@ -6750,7 +6746,7 @@
-
+
@@ -6759,9 +6755,9 @@
-
-
-
+
+
+
@@ -6769,19 +6765,19 @@
-
+
-
+
-
+
-
+
@@ -6794,7 +6790,7 @@
-
+
@@ -6842,66 +6838,66 @@
-
-
-
+
+
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
+
@@ -6917,7 +6913,7 @@
-
+
@@ -6925,7 +6921,7 @@
-
+
@@ -6943,12 +6939,12 @@
-
+
-
+
@@ -7015,17 +7011,17 @@
-
+
-
+
-
+
@@ -7044,11 +7040,11 @@
-
-
-
+
+
+
-
+
@@ -7057,28 +7053,28 @@
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
@@ -7087,29 +7083,29 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
@@ -7199,7 +7195,7 @@
-
+
@@ -7231,7 +7227,7 @@
-
+
@@ -7309,7 +7305,7 @@
-
+
@@ -7333,7 +7329,7 @@
-
+
@@ -7342,13 +7338,13 @@
-
+
-
+
@@ -7359,8 +7355,8 @@
-
-
+
+
@@ -7517,7 +7513,7 @@
-
+
@@ -7757,7 +7753,7 @@
-
+
@@ -7771,7 +7767,7 @@
-
+
@@ -7779,8 +7775,8 @@
-
-
+
+
@@ -7788,15 +7784,15 @@
-
+
-
-
-
+
+
+
@@ -7806,7 +7802,7 @@
-
+
@@ -7828,7 +7824,7 @@
-
+
@@ -7857,7 +7853,7 @@
-
+
@@ -7971,7 +7967,7 @@
-
+
@@ -7997,8 +7993,8 @@
-
-
+
+
@@ -8013,7 +8009,7 @@
-
+
@@ -8031,21 +8027,21 @@
-
-
+
+
-
+
-
-
+
+
-
+
@@ -8053,8 +8049,8 @@
-
-
+
+
@@ -8078,69 +8074,69 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
@@ -8164,7 +8160,7 @@
-
+
@@ -8178,8 +8174,8 @@
-
-
+
+
@@ -8209,8 +8205,8 @@
-
-
+
+
@@ -8256,19 +8252,19 @@
-
-
+
+
-
+
-
+
@@ -8304,11 +8300,11 @@
-
-
-
+
+
+
-
+
@@ -8320,7 +8316,7 @@
-
+
@@ -8335,15 +8331,15 @@
-
+
-
+
-
+
-
+
@@ -8355,7 +8351,7 @@
-
+
@@ -8370,22 +8366,22 @@
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
@@ -8398,36 +8394,36 @@
-
+
-
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
@@ -8439,14 +8435,14 @@
-
+
-
+
@@ -8455,11 +8451,11 @@
-
-
+
+
-
+
@@ -8467,17 +8463,17 @@
-
+
-
+
-
+
@@ -8485,27 +8481,27 @@
-
+
-
+
-
-
+
+
-
+
-
+
-
+
@@ -8517,37 +8513,37 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
@@ -8571,7 +8567,7 @@
-
+
@@ -8599,15 +8595,15 @@
-
-
+
+
-
+
@@ -8616,14 +8612,14 @@
-
+
-
+
@@ -8669,7 +8665,7 @@
-
+
@@ -8680,31 +8676,31 @@
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
@@ -8713,41 +8709,41 @@
-
+
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
-
-
+
+
+
@@ -8766,7 +8762,7 @@
-
+
@@ -8774,7 +8770,7 @@
-
+
@@ -8810,7 +8806,7 @@
-
+
@@ -8818,7 +8814,7 @@
-
+
@@ -8849,25 +8845,25 @@
-
+
-
+
-
+
-
+
-
+
@@ -8916,119 +8912,119 @@
-
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
@@ -9076,7 +9072,7 @@
-
+
@@ -9088,12 +9084,12 @@
-
+
-
+
-
+
@@ -9102,8 +9098,8 @@
-
-
+
+
@@ -9111,9 +9107,9 @@
-
+
-
+
@@ -9128,24 +9124,24 @@
-
+
-
+
-
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -9219,11 +9215,11 @@
-
+
-
+
@@ -9235,19 +9231,19 @@
-
+
-
+
-
+
@@ -9274,13 +9270,13 @@
-
+
-
+
@@ -9303,20 +9299,20 @@
-
+
-
+
-
+
-
+
-
+
@@ -9328,15 +9324,15 @@
-
-
+
+
-
-
-
+
+
+
@@ -9356,7 +9352,7 @@
-
+
@@ -9388,7 +9384,7 @@
-
+
@@ -9417,17 +9413,17 @@
-
+
-
+
-
+
@@ -9440,7 +9436,7 @@
-
+
@@ -9473,16 +9469,16 @@
-
-
+
+
-
+
-
+
@@ -9492,7 +9488,7 @@
-
+
@@ -9501,7 +9497,7 @@
-
+
@@ -9535,10 +9531,10 @@
-
+
-
+
@@ -9550,327 +9546,327 @@
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -9882,147 +9878,147 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
@@ -10037,65 +10033,65 @@
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
@@ -10103,192 +10099,192 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -10301,12 +10297,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -10331,17 +10327,17 @@
-
+
-
+
-
-
+
+
-
+
@@ -10356,7 +10352,7 @@
-
+
@@ -10366,7 +10362,7 @@
-
+
@@ -10444,11 +10440,11 @@
-
+
-
+
@@ -10456,21 +10452,21 @@
-
-
+
+
-
-
+
+
-
+
-
+
@@ -10490,7 +10486,7 @@
-
+
@@ -10575,9 +10571,9 @@
-
+
-
+
@@ -10589,8 +10585,8 @@
-
-
+
+
@@ -10601,7 +10597,7 @@
-
+
@@ -10610,10 +10606,10 @@
-
-
+
+
-
+
@@ -10621,10 +10617,10 @@
-
-
+
+
-
+
@@ -10636,7 +10632,7 @@
-
+
@@ -10645,15 +10641,15 @@
-
+
-
-
-
+
+
+
@@ -10665,12 +10661,12 @@
-
+
-
+
@@ -10693,7 +10689,7 @@
-
+
@@ -10715,7 +10711,7 @@
-
+
@@ -10723,8 +10719,8 @@
-
-
+
+
@@ -10738,14 +10734,14 @@
-
+
-
-
-
+
+
+
-
+
@@ -10755,11 +10751,11 @@
-
+
-
+
@@ -10767,21 +10763,21 @@
-
+
-
+
-
+
-
+
@@ -10805,9 +10801,9 @@
-
+
-
+
@@ -10818,41 +10814,41 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -10864,7 +10860,7 @@
-
+
@@ -10876,13 +10872,13 @@
-
+
-
-
-
-
+
+
+
+
@@ -10919,22 +10915,22 @@
-
-
+
+
-
+
-
+
-
+
@@ -10959,13 +10955,13 @@
-
-
+
+
-
+
@@ -11029,7 +11025,7 @@
-
+
@@ -11069,7 +11065,7 @@
-
+
@@ -11094,18 +11090,18 @@
-
+
-
+
-
+
@@ -11131,19 +11127,19 @@
-
+
-
+
-
+
@@ -11164,30 +11160,30 @@
-
-
-
+
+
+
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
+
@@ -11195,36 +11191,36 @@
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
+
@@ -11236,10 +11232,10 @@
-
-
-
-
+
+
+
+
@@ -11261,42 +11257,42 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -11323,7 +11319,7 @@
-
+
@@ -11332,33 +11328,33 @@
-
+
+
-
-
-
-
+
+
+
-
+
-
+
-
+
-
+
@@ -11370,8 +11366,8 @@
-
-
+
+
@@ -11399,43 +11395,43 @@
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
@@ -11451,7 +11447,7 @@
-
+
@@ -11463,453 +11459,453 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+
@@ -11931,9 +11927,9 @@
-
+
-
+
@@ -11945,22 +11941,22 @@
-
+
-
+
-
+
-
-
+
+
-
+
@@ -11991,36 +11987,36 @@
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -12028,11 +12024,11 @@
-
+
-
-
+
+
@@ -12040,20 +12036,20 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -12061,11 +12057,11 @@
-
+
-
-
+
+
@@ -12073,11 +12069,11 @@
-
+
-
-
+
+
@@ -12085,11 +12081,11 @@
-
+
-
-
+
+
@@ -12097,11 +12093,11 @@
-
+
-
-
+
+
@@ -12109,11 +12105,11 @@
-
+
-
-
+
+
@@ -12121,11 +12117,11 @@
-
+
-
-
+
+
@@ -12133,11 +12129,11 @@
-
+
-
-
+
+
@@ -12145,66 +12141,66 @@
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -12235,12 +12231,12 @@
-
+
-
+
-
+
@@ -12255,286 +12251,286 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -12549,28 +12545,28 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -12585,26 +12581,26 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -12615,12 +12611,12 @@
-
+
-
+
-
+
@@ -12635,117 +12631,117 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -12760,58 +12756,58 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -12819,267 +12815,267 @@
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
-
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
@@ -13098,26 +13094,26 @@
-
+
+
-
-
+
-
+
-
+
-
+
@@ -13128,7 +13124,7 @@
-
+
@@ -13152,13 +13148,13 @@
-
+
-
+
@@ -13166,24 +13162,24 @@
-
+
-
+
-
+
+
-
@@ -13192,8 +13188,8 @@
+
-
@@ -13201,11 +13197,11 @@
-
-
-
+
+
+
-
+
@@ -13221,7 +13217,7 @@
-
+
@@ -13241,7 +13237,7 @@
-
+
@@ -13249,7 +13245,7 @@
-
+
@@ -13265,18 +13261,18 @@
-
+
-
+
-
+
@@ -13284,8 +13280,8 @@
-
-
+
+
@@ -13298,71 +13294,71 @@
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
-
+
+
@@ -13370,8 +13366,8 @@
-
-
+
+
@@ -13379,1099 +13375,1099 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
+
+
-
-
-
-
+
+
-
-
-
+
+
+
-
-
+
+
+
+
-
-
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
+
+
+
-
-
-
-
+
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
+
+
-
+
@@ -14479,8 +14475,8 @@
-
-
+
+
@@ -14497,150 +14493,150 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
+
@@ -14652,11 +14648,11 @@
-
+
-
-
+
+
@@ -14670,16 +14666,16 @@
-
-
+
+
-
+
-
+
-
+
@@ -14703,178 +14699,178 @@
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
@@ -14882,11 +14878,11 @@
-
+
-
-
+
+
@@ -14900,8 +14896,8 @@
-
-
+
+
@@ -14909,11 +14905,11 @@
-
+
-
-
+
+
@@ -14960,7 +14956,7 @@
-
+
@@ -14984,16 +14980,16 @@
-
+
-
+
-
+
-
+
@@ -15005,17 +15001,17 @@
-
+
-
+
-
-
-
+
+
+
@@ -15023,15 +15019,15 @@
-
-
+
+
-
-
-
-
+
+
+
+
@@ -15042,7 +15038,7 @@
-
+
@@ -15054,7 +15050,7 @@
-
+
@@ -15063,12 +15059,12 @@
-
-
-
-
+
+
+
+
-
+
@@ -15116,14 +15112,14 @@
-
+
-
+
-
-
+
+
@@ -15131,17 +15127,17 @@
-
-
-
+
+
+
-
+
-
+
@@ -15149,8 +15145,8 @@
-
-
+
+
@@ -15182,8 +15178,8 @@
-
-
+
+
@@ -15248,16 +15244,16 @@
-
+
-
+
-
+
-
+
@@ -15344,7 +15340,7 @@
-
+
@@ -15392,43 +15388,43 @@
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
@@ -15437,7 +15433,7 @@
-
+
@@ -15455,191 +15451,191 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
@@ -15653,49 +15649,49 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -15704,31 +15700,31 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -15737,28 +15733,28 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -15776,47 +15772,47 @@
-
+
-
+
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
+
@@ -15840,48 +15836,48 @@
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
@@ -15890,11 +15886,11 @@
-
+
-
-
+
+
@@ -15905,10 +15901,10 @@
-
+
-
+
@@ -15935,13 +15931,13 @@
-
+
-
+
-
+
@@ -15974,7 +15970,7 @@
-
+
@@ -15995,7 +15991,7 @@
-
+
@@ -16004,7 +16000,7 @@
-
+
@@ -16019,13 +16015,13 @@
-
+
-
+
-
+
@@ -16051,17 +16047,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -16069,13 +16065,13 @@
-
+
-
-
+
+
-
+
@@ -16084,10 +16080,10 @@
-
+
-
+
@@ -16104,10 +16100,10 @@
-
-
+
+
-
+
@@ -16116,18 +16112,18 @@
-
-
-
-
+
+
+
+
-
+
-
+
@@ -16136,7 +16132,7 @@
-
+
@@ -16153,10 +16149,10 @@
-
-
-
-
+
+
+
+
@@ -16164,25 +16160,25 @@
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
@@ -16198,20 +16194,20 @@
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -16219,11 +16215,11 @@
-
+
-
-
+
+
@@ -16231,11 +16227,11 @@
-
+
-
-
+
+
@@ -16243,11 +16239,11 @@
-
+
-
-
+
+
@@ -16255,11 +16251,11 @@
-
+
-
-
+
+
@@ -16291,12 +16287,12 @@
-
+
-
+
-
+
@@ -16311,349 +16307,349 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -16668,12 +16664,12 @@
-
+
-
+
-
+
@@ -16688,9 +16684,9 @@
-
+
-
+
@@ -17434,16 +17430,16 @@
-
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -17451,43 +17447,43 @@
-
+
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
+
-
+
@@ -17498,13 +17494,13 @@
-
-
+
+
-
+
-
+
@@ -17516,11 +17512,11 @@
-
+
-
-
+
+
@@ -17528,21 +17524,21 @@
-
+
-
+
-
-
+
+
-
+
-
+
-
+
@@ -17551,64 +17547,64 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -17619,7 +17615,7 @@
-
+
@@ -17669,7 +17665,7 @@
-
+
@@ -17698,7 +17694,7 @@
-
+
@@ -17724,7 +17720,7 @@
-
+
@@ -17759,7 +17755,7 @@
-
+
@@ -17767,7 +17763,7 @@
-
+
@@ -17781,7 +17777,7 @@
-
+
@@ -17792,7 +17788,7 @@
-
+
@@ -17803,7 +17799,7 @@
-
+
@@ -17817,7 +17813,7 @@
-
+
@@ -17828,7 +17824,7 @@
-
+
@@ -17836,7 +17832,7 @@
-
+
@@ -17847,7 +17843,7 @@
-
+
@@ -17858,7 +17854,7 @@
-
+