Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Doc/library/zoneinfo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ The ``ZoneInfo`` class has two alternate constructors:

Objects created via this constructor cannot be pickled (see `pickling`_).

:exc:`ValueError` is raised if the data read from *file_obj* is not a valid
TZif file.

.. classmethod:: ZoneInfo.no_cache(key)

An alternate constructor that bypasses the constructor's cache. It is
Expand Down
10 changes: 6 additions & 4 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -854,11 +854,13 @@ Optimizations

* Builds using Visual Studio 2026 (MSVC 18) may now use the new
:ref:`tail-calling interpreter <whatsnew314-tail-call-interpreter>`.
Results on an early experimental MSVC compiler reported roughly 15% speedup
on the geometric mean of pyperformance on Windows x86-64 over
the switch-case interpreter. We have
observed speedups ranging from 15% for large pure-Python libraries
Results on Visual Studio 18.1.1 report between
`15-20% <https://github.com/faster-cpython/ideas/blob/main/results/5800X-msvc.pgo2-vs-msvc.pgo.tc.svg>`__
speedup on the geometric mean of pyperformance on Windows x86-64 over
the switch-case interpreter on an AMD Ryzen 7 5800X. We have
observed speedups ranging from 14% for large pure-Python libraries
to 40% for long-running small pure-Python scripts on Windows.
This was made possible by a new feature introduced in MSVC 18.
(Contributed by Chris Eibl, Ken Jin, and Brandt Bucher in :gh:`143068`.
Special thanks to the MSVC team including Hulon Jenkins.)

Expand Down
1 change: 0 additions & 1 deletion Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ extern int _PyPerfTrampoline_SetCallbacks(_PyPerf_Callbacks *);
extern void _PyPerfTrampoline_GetCallbacks(_PyPerf_Callbacks *);
extern int _PyPerfTrampoline_Init(int activate);
extern int _PyPerfTrampoline_Fini(void);
extern void _PyPerfTrampoline_FreeArenas(void);
extern int _PyIsPerfTrampolineActive(void);
extern PyStatus _PyPerfTrampoline_AfterFork_Child(void);
#ifdef PY_HAVE_PERF_TRAMPOLINE
Expand Down
4 changes: 3 additions & 1 deletion Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ struct _ceval_runtime_state {
struct trampoline_api_st trampoline_api;
FILE *map_file;
Py_ssize_t persist_after_fork;
_PyFrameEvalFunction prev_eval_frame;
_PyFrameEvalFunction prev_eval_frame;
Py_ssize_t trampoline_refcount;
int code_watcher_id;
#else
int _not_used;
#endif
Expand Down
2 changes: 1 addition & 1 deletion Lib/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -2140,7 +2140,7 @@ def _communicate(self, input, endtime, orig_timeout):

while selector.get_map():
timeout = self._remaining_time(endtime)
if timeout is not None and timeout < 0:
if timeout is not None and timeout <= 0:
self._check_timeout(endtime, orig_timeout,
stdout, stderr,
skip_check_and_raise=True)
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2104,6 +2104,23 @@ def make_case():
with self.assertRaises(BufferError):
ba.rsplit(evil)

def test_extend_empty_buffer_overflow(self):
# gh-143003
class EvilIter:
def __iter__(self):
return self
def __next__(self):
return next(source)
def __length_hint__(self):
return 0

# Use ASCII digits so float() takes the fast path that expects a NUL terminator.
source = iter(b'42')
ba = bytearray()
ba.extend(EvilIter())

self.assertRaises(ValueError, float, bytearray())

def test_hex_use_after_free(self):
# Prevent UAF in bytearray.hex(sep) with re-entrant sep.__len__.
# Regression test for https://github.com/python/cpython/issues/143195.
Expand Down
7 changes: 6 additions & 1 deletion Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1752,7 +1752,12 @@ def main_work():
unwinder_gil = RemoteUnwinder(
p.pid, only_active_thread=True
)
gil_traces = _get_stack_trace_with_retry(unwinder_gil)
# Use condition to retry until we capture a thread holding the GIL
# (sampling may catch moments with no GIL holder on slow CI)
gil_traces = _get_stack_trace_with_retry(
unwinder_gil,
condition=lambda t: sum(len(i.threads) for i in t) >= 1,
)

# Count threads
total_threads = sum(
Expand Down
8 changes: 7 additions & 1 deletion Lib/test/test_mmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,13 @@ def test_flush_parameters(self):
if hasattr(mmap, 'MS_INVALIDATE'):
m.flush(PAGESIZE * 2, flags=mmap.MS_INVALIDATE)
if hasattr(mmap, 'MS_ASYNC') and hasattr(mmap, 'MS_INVALIDATE'):
m.flush(0, PAGESIZE, flags=mmap.MS_ASYNC | mmap.MS_INVALIDATE)
if sys.platform == 'freebsd':
# FreeBSD doesn't support this combination
with self.assertRaises(OSError) as cm:
m.flush(0, PAGESIZE, flags=mmap.MS_ASYNC | mmap.MS_INVALIDATE)
self.assertEqual(cm.exception.errno, errno.EINVAL)
else:
m.flush(0, PAGESIZE, flags=mmap.MS_ASYNC | mmap.MS_INVALIDATE)

@unittest.skipUnless(sys.platform == 'linux', 'Linux only')
@support.requires_linux_version(5, 17, 0)
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_zoneinfo/test_zoneinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ def test_bad_zones(self):
bad_zones = [
b"", # Empty file
b"AAAA3" + b" " * 15, # Bad magic
# Truncated V2 file (should not loop indefinitely)
b"TZif2" + (b"\x00" * 39) + b"TZif2" + (b"\x00" * 39) + b"\n" + b"Part",
]

for bad_zone in bad_zones:
Expand Down
9 changes: 4 additions & 5 deletions Lib/zoneinfo/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,10 @@ def get_abbr(idx):
c = fobj.read(1) # Should be \n
assert c == b"\n", c

tz_bytes = b""
while (c := fobj.read(1)) != b"\n":
tz_bytes += c

tz_str = tz_bytes
line = fobj.readline()
if not line.endswith(b"\n"):
raise ValueError("Invalid TZif file: unexpected end of file")
tz_str = line.rstrip(b"\n")
else:
tz_str = None

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix an overflow of the shared empty buffer in :meth:`bytearray.extend` when
``__length_hint__()`` returns 0 for non-empty iterator.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix use-after-free in perf trampoline when toggling profiling while
threads are running or during interpreter finalization with daemon threads
active. The fix uses reference counting to ensure trampolines are not freed
while any code object could still reference them. Pach by Pablo Galindo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Updated timeout evaluation logic in :mod:`subprocess` to be compatible with deterministic environments like Shadow where time moves exactly as requested.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`zoneinfo`: fix infinite loop in :meth:`ZoneInfo.from_file
<zoneinfo.ZoneInfo.from_file>` when parsing a malformed TZif file. Patch by Fatih Celik.
4 changes: 2 additions & 2 deletions Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,6 @@ bytearray_extend_impl(PyByteArrayObject *self, PyObject *iterable_of_ints)
Py_DECREF(bytearray_obj);
return NULL;
}
buf[len++] = value;
Py_DECREF(item);

if (len >= buf_size) {
Expand All @@ -2233,7 +2232,7 @@ bytearray_extend_impl(PyByteArrayObject *self, PyObject *iterable_of_ints)
Py_DECREF(bytearray_obj);
return PyErr_NoMemory();
}
addition = len >> 1;
addition = len ? len >> 1 : 1;
if (addition > PyByteArray_SIZE_MAX - len)
buf_size = PyByteArray_SIZE_MAX;
else
Expand All @@ -2247,6 +2246,7 @@ bytearray_extend_impl(PyByteArrayObject *self, PyObject *iterable_of_ints)
have invalidated it. */
buf = PyByteArray_AS_STRING(bytearray_obj);
}
buf[len++] = value;
}
Py_DECREF(it);

Expand Down
67 changes: 59 additions & 8 deletions Python/perf_trampoline.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,42 @@ enum perf_trampoline_type {
#define persist_after_fork _PyRuntime.ceval.perf.persist_after_fork
#define perf_trampoline_type _PyRuntime.ceval.perf.perf_trampoline_type
#define prev_eval_frame _PyRuntime.ceval.perf.prev_eval_frame
#define trampoline_refcount _PyRuntime.ceval.perf.trampoline_refcount
#define code_watcher_id _PyRuntime.ceval.perf.code_watcher_id

static void free_code_arenas(void);

static void
perf_trampoline_reset_state(void)
{
free_code_arenas();
if (code_watcher_id >= 0) {
PyCode_ClearWatcher(code_watcher_id);
code_watcher_id = -1;
}
extra_code_index = -1;
}

static int
perf_trampoline_code_watcher(PyCodeEvent event, PyCodeObject *co)
{
if (event != PY_CODE_EVENT_DESTROY) {
return 0;
}
if (extra_code_index == -1) {
return 0;
}
py_trampoline f = NULL;
int ret = _PyCode_GetExtra((PyObject *)co, extra_code_index, (void **)&f);
if (ret != 0 || f == NULL) {
return 0;
}
trampoline_refcount--;
if (trampoline_refcount == 0) {
perf_trampoline_reset_state();
}
return 0;
}

static void
perf_map_write_entry(void *state, const void *code_addr,
Expand Down Expand Up @@ -407,6 +443,7 @@ py_trampoline_evaluator(PyThreadState *ts, _PyInterpreterFrame *frame,
perf_code_arena->code_size, co);
_PyCode_SetExtra((PyObject *)co, extra_code_index,
(void *)new_trampoline);
trampoline_refcount++;
f = new_trampoline;
}
assert(f != NULL);
Expand All @@ -433,6 +470,7 @@ int PyUnstable_PerfTrampoline_CompileCode(PyCodeObject *co)
}
trampoline_api.write_state(trampoline_api.state, new_trampoline,
perf_code_arena->code_size, co);
trampoline_refcount++;
return _PyCode_SetExtra((PyObject *)co, extra_code_index,
(void *)new_trampoline);
}
Expand Down Expand Up @@ -487,6 +525,10 @@ _PyPerfTrampoline_Init(int activate)
{
#ifdef PY_HAVE_PERF_TRAMPOLINE
PyThreadState *tstate = _PyThreadState_GET();
if (code_watcher_id == 0) {
// Initialize to -1 since 0 is a valid watcher ID
code_watcher_id = -1;
}
if (!activate) {
_PyInterpreterState_SetEvalFrameFunc(tstate->interp, prev_eval_frame);
perf_status = PERF_STATUS_NO_INIT;
Expand All @@ -504,6 +546,13 @@ _PyPerfTrampoline_Init(int activate)
if (new_code_arena() < 0) {
return -1;
}
code_watcher_id = PyCode_AddWatcher(perf_trampoline_code_watcher);
if (code_watcher_id < 0) {
PyErr_FormatUnraisable("Failed to register code watcher for perf trampoline");
free_code_arenas();
return -1;
}
trampoline_refcount = 1; // Base refcount held by the system
perf_status = PERF_STATUS_OK;
}
#endif
Expand All @@ -525,17 +574,19 @@ _PyPerfTrampoline_Fini(void)
trampoline_api.free_state(trampoline_api.state);
perf_trampoline_type = PERF_TRAMPOLINE_UNSET;
}
extra_code_index = -1;

// Prevent new trampolines from being created
perf_status = PERF_STATUS_NO_INIT;
#endif
return 0;
}

void _PyPerfTrampoline_FreeArenas(void) {
#ifdef PY_HAVE_PERF_TRAMPOLINE
free_code_arenas();
// Decrement base refcount. If refcount reaches 0, all code objects are already
// dead so clean up now. Otherwise, watcher remains active to clean up when last
// code object dies; extra_code_index stays valid so watcher can identify them.
trampoline_refcount--;
if (trampoline_refcount == 0) {
perf_trampoline_reset_state();
}
#endif
return;
return 0;
}

int
Expand Down
1 change: 0 additions & 1 deletion Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1944,7 +1944,6 @@ finalize_interp_clear(PyThreadState *tstate)
_PyArg_Fini();
_Py_ClearFileSystemEncoding();
_PyPerfTrampoline_Fini();
_PyPerfTrampoline_FreeArenas();
}

finalize_interp_types(tstate->interp);
Expand Down
Loading