Skip to content

Commit 287fd31

Browse files
itamarometa-codesync[bot]
authored andcommitted
Import CPython 3.14.8+ stable branch (2026-10-05)
Summary: Imported python/cpython `3.14.8+` from upstream rev [`e0c28e2`](https://www.github.com/python/cpython/commit/e0c28e2a00746e9100969868a71f0e5524450ded) (committed 2026-10-05 10:24:22+00:00). # Commit Info - Base: (`3.14.8+`) - [`5c33a3b`](https://www.github.com/python/cpython/commit/5c33a3b689f2bade4cf17e0941ab8b3f072633a6) (commit date: 2026-10-05 01:57:30+00:00) - Imported: (`3.14.8+`) - [`e0c28e2`](https://www.github.com/python/cpython/commit/e0c28e2a00746e9100969868a71f0e5524450ded) (commit date: 2026-10-05 10:24:22+00:00) # Noteworthy file changes - Low-signal files (1 added) (NEWS.d, docs, .github) Complete list of added/removed files: https://www.internalfb.com/intern/everpaste/?color=0&handle=GLUlGDLXXddiUicJAGb4qzeO0blqbr0LAAAz Reviewed By: DinoV Differential Revision: D123606344 fbshipit-source-id: 60613adb5981ca9c4702a4bc69aefa3d6d02ef60
1 parent 3a6eaf0 commit 287fd31

19 files changed

Lines changed: 303 additions & 25 deletions

‎.github/workflows/build.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,9 @@ jobs:
589589
- check-name: Undefined behavior
590590
sanitizer: UBSan
591591
free-threading: false
592+
- check-name: Memory
593+
sanitizer: MSan
594+
free-threading: false
592595
uses: ./.github/workflows/reusable-san.yml
593596
with:
594597
sanitizer: ${{ matrix.sanitizer }}

‎.github/workflows/reusable-san.yml‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,26 @@ jobs:
6767
|| ''
6868
}}
6969
- name: UBSan option setup
70-
if: inputs.sanitizer != 'TSan'
70+
if: inputs.sanitizer == 'UBSan'
7171
run: >-
7272
echo
7373
"UBSAN_OPTIONS=${SAN_LOG_OPTION}"
7474
>> "$GITHUB_ENV"
7575
env:
7676
SAN_LOG_OPTION: log_path=${{ github.workspace }}/san_log
77+
- name: MSan option setup
78+
if: inputs.sanitizer == 'MSan'
79+
run: |
80+
echo "MSAN_OPTIONS=${SAN_LOG_OPTION} allocator_may_return_null=1 handle_segv=0" >> "$GITHUB_ENV"
81+
# MSan reports false positives for memory initialized by libraries
82+
# that are not built with MSan, so disable modules that use them.
83+
{
84+
echo '*disabled*'
85+
echo '_bz2 _ctypes _curses _curses_panel _dbm _decimal _gdbm _hashlib'
86+
echo '_lzma _sqlite3 _ssl _tkinter _uuid _zstd readline zlib'
87+
} > Modules/Setup.local
88+
env:
89+
SAN_LOG_OPTION: log_path=${{ github.workspace }}/san_log
7790
- name: Add ccache to PATH
7891
run: |
7992
echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
@@ -98,6 +111,8 @@ jobs:
98111
# gh-157958: -O2 instead of the pydebug default -Og to avoid a clang 21
99112
# compile-time blowup on some interpreter files.
100113
# (https://github.com/llvm/llvm-project/issues/179695)
114+
# MSan uses --with-assertions instead of --with-pydebug because its
115+
# hooks on the Python memory allocators hide uninitialized reads.
101116
- name: Configure CPython
102117
run: >-
103118
./configure
@@ -106,9 +121,11 @@ jobs:
106121
${{
107122
inputs.sanitizer == 'TSan'
108123
&& '--with-thread-sanitizer'
124+
|| inputs.sanitizer == 'MSan'
125+
&& '--with-memory-sanitizer'
109126
|| '--with-undefined-behavior-sanitizer'
110127
}}
111-
--with-pydebug
128+
${{ inputs.sanitizer == 'MSan' && '--with-assertions' || '--with-pydebug' }}
112129
${{ inputs.sanitizer == 'TSan' && '--with-openssl="$OPENSSL_DIR" --with-openssl-rpath=auto' || '' }}
113130
${{ inputs.free-threading && '--disable-gil' || '' }}
114131
- name: Build CPython

‎Doc/using/configure.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,10 @@ Debug options
920920

921921
Enable MemorySanitizer allocation error detector, ``msan`` (default is no).
922922

923+
MSan reports false positives for memory initialized by libraries that are
924+
not built with MSan, so either build all dependencies with MSan or disable
925+
the extension modules that use them in :file:`Modules/Setup.local`.
926+
923927
.. versionadded:: 3.6
924928

925929
.. option:: --with-undefined-behavior-sanitizer

‎Include/pyport.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,8 @@ extern "C" {
577577
# if !defined(_Py_MEMORY_SANITIZER)
578578
# define _Py_MEMORY_SANITIZER
579579
# define _Py_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory))
580+
# define _Py_MSAN_UNPOISON(PTR, SIZE) (__msan_unpoison(PTR, SIZE))
581+
# define _Py_MSAN_UNPOISON_STRING(STR) (__msan_unpoison_string(STR))
580582
# endif
581583
# endif
582584
# if __has_feature(address_sanitizer)
@@ -615,6 +617,12 @@ extern "C" {
615617
#ifndef _Py_NO_SANITIZE_MEMORY
616618
# define _Py_NO_SANITIZE_MEMORY
617619
#endif
620+
#ifndef _Py_MSAN_UNPOISON
621+
# define _Py_MSAN_UNPOISON(PTR, SIZE)
622+
#endif
623+
#ifndef _Py_MSAN_UNPOISON_STRING
624+
# define _Py_MSAN_UNPOISON_STRING(STR)
625+
#endif
618626

619627
/* AIX has __bool__ redefined in it's system header file. */
620628
#if defined(_AIX) && defined(__bool__)

‎Lib/asyncio/tools.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def __init__(
2727
# ─── indexing helpers ───────────────────────────────────────────
2828
def _format_stack_entry(elem: str|FrameInfo) -> str:
2929
if not isinstance(elem, str):
30+
if elem.lineno is None:
31+
if elem.filename in ("", "~"):
32+
return f"{elem.funcname}"
33+
return f"{elem.funcname} {elem.filename}"
3034
if elem.lineno == 0 and elem.filename == "":
3135
return f"{elem.funcname}"
3236
else:
@@ -190,8 +194,7 @@ def build_task_table(result):
190194
# Build coroutine stack string
191195
frames = [frame for coro in task_info.coroutine_stack
192196
for frame in coro.call_stack]
193-
coro_stack = " -> ".join(_format_stack_entry(x).split(" ")[0]
194-
for x in frames)
197+
coro_stack = " -> ".join(x.funcname for x in frames)
195198

196199
# Handle tasks with no awaiters
197200
if not task_info.awaited_by:
@@ -202,8 +205,7 @@ def build_task_table(result):
202205
# Handle tasks with awaiters
203206
for coro_info in task_info.awaited_by:
204207
parent_id = coro_info.task_name
205-
awaiter_frames = [_format_stack_entry(x).split(" ")[0]
206-
for x in coro_info.call_stack]
208+
awaiter_frames = [x.funcname for x in coro_info.call_stack]
207209
awaiter_chain = " -> ".join(awaiter_frames)
208210
awaiter_name = id2name.get(parent_id, "Unknown")
209211
parent_id_str = (hex(parent_id) if isinstance(parent_id, int)

‎Lib/test/test_asyncio/test_tools.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,82 @@ def test_table_output_format(self):
15431543

15441544
class TestAsyncioToolsEdgeCases(unittest.TestCase):
15451545

1546+
def test_frames_without_location_tree(self):
1547+
"""Frames the unwinder could not fully read - should not crash."""
1548+
input_ = [
1549+
AwaitedInfo(
1550+
thread_id=1,
1551+
awaited_by=[
1552+
TaskInfo(
1553+
task_id=1,
1554+
task_name="Task-A",
1555+
coroutine_stack=[
1556+
CoroInfo(
1557+
call_stack=[
1558+
FrameInfo("<unreadable frame>", "~", None),
1559+
FrameInfo("<unknown function>", "app.py", None),
1560+
FrameInfo("big", "big.py", None),
1561+
],
1562+
task_name=1
1563+
)
1564+
],
1565+
awaited_by=[]
1566+
)
1567+
]
1568+
)
1569+
]
1570+
self.assertEqual(
1571+
tools.build_async_tree(input_),
1572+
[[
1573+
"└── (T) Task-A",
1574+
" └── big big.py",
1575+
" └── <unknown function> app.py",
1576+
" └── <unreadable frame>",
1577+
]],
1578+
)
1579+
1580+
def test_frames_without_location_table(self):
1581+
"""Frame names are not truncated at the first space."""
1582+
input_ = [
1583+
AwaitedInfo(
1584+
thread_id=1,
1585+
awaited_by=[
1586+
TaskInfo(
1587+
task_id=1,
1588+
task_name="Task-A",
1589+
coroutine_stack=[
1590+
CoroInfo(
1591+
call_stack=[
1592+
FrameInfo("<unreadable frame>", "~", None)
1593+
],
1594+
task_name=1
1595+
)
1596+
],
1597+
awaited_by=[
1598+
CoroInfo(
1599+
call_stack=[
1600+
FrameInfo("<unknown function>", "app.py", None)
1601+
],
1602+
task_name=2
1603+
)
1604+
]
1605+
)
1606+
]
1607+
)
1608+
]
1609+
self.assertEqual(
1610+
tools.build_task_table(input_),
1611+
[[
1612+
1,
1613+
"0x1",
1614+
"Task-A",
1615+
"<unreadable frame>",
1616+
"<unknown function>",
1617+
"Unknown",
1618+
"0x2",
1619+
]],
1620+
)
1621+
15461622
def test_task_awaits_self(self):
15471623
"""A task directly awaits itself - should raise a cycle."""
15481624
input_ = [

‎Lib/test/test_external_inspection.py‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,99 @@ def _get_awaited_by_relationships(self, stack_trace):
15981598

15991599

16001600

1601+
@skip_if_not_supported
1602+
@unittest.skipIf(
1603+
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
1604+
"Test only runs on Linux with process_vm_readv support",
1605+
)
1606+
class TestMetadataDegradation(RemoteInspectionTestBase):
1607+
"""Tests for graceful degradation of oversized code-object metadata."""
1608+
1609+
def _find_frame_in_trace(self, stack_trace, predicate):
1610+
for thread_info in stack_trace:
1611+
for frame in thread_info.frame_info:
1612+
if predicate(frame):
1613+
return frame
1614+
return None
1615+
1616+
def test_long_qualname_truncated_not_dropped(self):
1617+
"""A qualname longer than 1024 chars is truncated instead of
1618+
failing the whole sample."""
1619+
name = "f" * 1100
1620+
src = f"def {name}(sample):\n return sample()\n"
1621+
ns = {}
1622+
exec(src, ns)
1623+
1624+
trace = ns[name](RemoteUnwinder(os.getpid()).get_stack_trace)
1625+
frame = self._find_frame_in_trace(
1626+
trace, lambda f: f.funcname.startswith("fff")
1627+
)
1628+
self.assertIsNotNone(frame)
1629+
self.assertEqual(frame.funcname, "f" * 1024)
1630+
1631+
def test_long_filename_truncated(self):
1632+
"""A filename longer than 1024 chars is truncated instead of
1633+
failing the whole sample."""
1634+
src = "def g(sample):\n return sample()\n"
1635+
ns = {}
1636+
exec(compile(src, "x" * 1500 + ".py", "exec"), ns)
1637+
1638+
trace = ns["g"](RemoteUnwinder(os.getpid()).get_stack_trace)
1639+
frame = self._find_frame_in_trace(trace, lambda f: f.funcname == "g")
1640+
self.assertIsNotNone(frame)
1641+
self.assertEqual(frame.filename, "x" * 1024)
1642+
1643+
def test_oversized_linetable_degrades_to_no_location(self):
1644+
"""A linetable over the 4096-byte limit degrades to a frame without
1645+
location instead of failing the whole sample."""
1646+
src = (
1647+
"def big(sample):\n"
1648+
+ " x = 1\n" * 1200
1649+
+ " return sample()\n"
1650+
)
1651+
ns = {}
1652+
exec(compile(src, "big_linetable.py", "exec"), ns)
1653+
big = ns["big"]
1654+
self.assertGreater(len(big.__code__.co_linetable), 4096)
1655+
1656+
trace = big(RemoteUnwinder(os.getpid()).get_stack_trace)
1657+
frame = self._find_frame_in_trace(
1658+
trace, lambda f: f.funcname == "big"
1659+
)
1660+
self.assertIsNone(frame.lineno)
1661+
self.assertEqual(frame.filename, "big_linetable.py")
1662+
1663+
@unittest.skipUnless(
1664+
sys.platform == "linux",
1665+
"Process death maps to ProcessLookupError only on Linux in 3.14",
1666+
)
1667+
def test_dead_process_raises_not_degrades(self):
1668+
"""Death of the target raises ProcessLookupError instead of
1669+
degrading to synthetic frames."""
1670+
script_body = """\
1671+
import time
1672+
sock.sendall(b"ready")
1673+
time.sleep(10_000)
1674+
"""
1675+
with self._target_process(script_body) as (p, client_socket, make_unwinder):
1676+
_wait_for_signal(client_socket, b"ready")
1677+
unwinder = make_unwinder()
1678+
unwinder.get_stack_trace()
1679+
1680+
p.kill()
1681+
p.wait()
1682+
1683+
for _ in busy_retry(SHORT_TIMEOUT, error=False):
1684+
try:
1685+
unwinder.get_stack_trace()
1686+
except ProcessLookupError:
1687+
break
1688+
except RuntimeError:
1689+
continue
1690+
else:
1691+
self.fail("ProcessLookupError never raised for dead process")
1692+
1693+
16011694
class TestFrameChainLimits(RemoteInspectionTestBase):
16021695
"""Frame chain walks abort instead of looping/overflowing on deep chains."""
16031696

‎Lib/test/test_faulthandler.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434

3535

3636
def skip_if_sanitizer_signal(signame):
37-
return support.skip_if_sanitizer(f"TSAN/UBSan itercepts {signame}",
38-
thread=True, ub=True)
37+
return support.skip_if_sanitizer(f"TSan/UBSan/MSan intercepts {signame}",
38+
thread=True, ub=True, memory=True)
3939

4040

4141
def expected_traceback(lineno1, lineno2, header, min_count=1):

‎Lib/test/test_xxtestfuzz.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def test_sample_input_smoke_test(self):
1818
_xxtestfuzz.run(b"1")
1919
_xxtestfuzz.run(b"AAAAAAA")
2020
_xxtestfuzz.run(b"AAAAAA\0")
21+
_xxtestfuzz.run(b"\xff\0")
2122

2223

2324
if __name__ == "__main__":
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the sampling profiler dropping entire samples when a non-fatal read fails;
2+
frames now keep any readable metadata, and long funcnames and filenames are
3+
truncated instead. Patch by Maurycy Pawłowski-Wieroński.

0 commit comments

Comments
 (0)