Skip to content

Commit 8da06ea

Browse files
committed
Merge branch 'main' into refactor/hashlib/module-methods-135532
2 parents e882157 + e5f03b9 commit 8da06ea

File tree

9 files changed

+133
-68
lines changed

9 files changed

+133
-68
lines changed

Doc/whatsnew/3.14.rst

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,16 @@ os
17571757
(Contributed by Cody Maloney in :gh:`129205`.)
17581758

17591759

1760+
os.path
1761+
-------
1762+
1763+
* The *strict* parameter to :func:`os.path.realpath` accepts a new value,
1764+
:data:`os.path.ALLOW_MISSING`.
1765+
If used, errors other than :exc:`FileNotFoundError` will be re-raised;
1766+
the resulting path can be missing but it will be free of symlinks.
1767+
(Contributed by Petr Viktorin for :cve:`2025-4517`.)
1768+
1769+
17601770
pathlib
17611771
-------
17621772

@@ -1945,6 +1955,28 @@ sysconfig
19451955
(Contributed by Xuehai Pan in :gh:`131799`.)
19461956

19471957

1958+
tarfile
1959+
-------
1960+
1961+
* :func:`~tarfile.data_filter` now normalizes symbolic link targets in order to
1962+
avoid path traversal attacks.
1963+
(Contributed by Petr Viktorin in :gh:`127987` and :cve:`2025-4138`.)
1964+
* :func:`~tarfile.TarFile.extractall` now skips fixing up directory attributes
1965+
when a directory was removed or replaced by another kind of file.
1966+
(Contributed by Petr Viktorin in :gh:`127987` and :cve:`2024-12718`.)
1967+
* :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall`
1968+
now (re-)apply the extraction filter when substituting a link (hard or
1969+
symbolic) with a copy of another archive member, and when fixing up
1970+
directory attributes.
1971+
The former raises a new exception, :exc:`~tarfile.LinkFallbackError`.
1972+
(Contributed by Petr Viktorin for :cve:`2025-4330` and :cve:`2024-12718`.)
1973+
* :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall`
1974+
no longer extract rejected members when
1975+
:func:`~tarfile.TarFile.errorlevel` is zero.
1976+
(Contributed by Matt Prodani and Petr Viktorin in :gh:`112887`
1977+
and :cve:`2025-4435`.)
1978+
1979+
19481980
threading
19491981
---------
19501982

@@ -2700,6 +2732,7 @@ New features
27002732
* :c:func:`PyUnicodeWriter_Discard`
27012733
* :c:func:`PyUnicodeWriter_Finish`
27022734
* :c:func:`PyUnicodeWriter_Format`
2735+
* :c:func:`PyUnicodeWriter_WriteASCII`
27032736
* :c:func:`PyUnicodeWriter_WriteChar`
27042737
* :c:func:`PyUnicodeWriter_WriteRepr`
27052738
* :c:func:`PyUnicodeWriter_WriteStr`
@@ -2976,7 +3009,7 @@ Deprecated
29763009
:c:func:`PyUnicodeWriter_WriteSubstring(writer, str, start, end) <PyUnicodeWriter_WriteSubstring>`.
29773010
* :c:func:`!_PyUnicodeWriter_WriteASCIIString`:
29783011
replace ``_PyUnicodeWriter_WriteASCIIString(&writer, str)`` with
2979-
:c:func:`PyUnicodeWriter_WriteUTF8(writer, str) <PyUnicodeWriter_WriteUTF8>`.
3012+
:c:func:`PyUnicodeWriter_WriteASCII(writer, str) <PyUnicodeWriter_WriteASCII>`.
29803013
* :c:func:`!_PyUnicodeWriter_WriteLatin1String`:
29813014
replace ``_PyUnicodeWriter_WriteLatin1String(&writer, str)`` with
29823015
:c:func:`PyUnicodeWriter_WriteUTF8(writer, str) <PyUnicodeWriter_WriteUTF8>`.

Lib/reprlib.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,22 @@ def repr_str(self, x, level):
181181
return s
182182

183183
def repr_int(self, x, level):
184-
s = builtins.repr(x) # XXX Hope this isn't too slow...
184+
try:
185+
s = builtins.repr(x)
186+
except ValueError as exc:
187+
assert 'sys.set_int_max_str_digits()' in str(exc)
188+
# Those imports must be deferred due to Python's build system
189+
# where the reprlib module is imported before the math module.
190+
import math, sys
191+
# Integers with more than sys.get_int_max_str_digits() digits
192+
# are rendered differently as their repr() raises a ValueError.
193+
# See https://github.com/python/cpython/issues/135487.
194+
k = 1 + int(math.log10(abs(x)))
195+
# Note: math.log10(abs(x)) may be overestimated or underestimated,
196+
# but for simplicity, we do not compute the exact number of digits.
197+
max_digits = sys.get_int_max_str_digits()
198+
return (f'<{x.__class__.__name__} instance with roughly {k} '
199+
f'digits (limit at {max_digits}) at 0x{id(x):x}>')
185200
if len(s) > self.maxlong:
186201
i = max(0, (self.maxlong-3)//2)
187202
j = max(0, self.maxlong-3-i)

Lib/test/libregrtest/main.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ def find_tests(self, tests: TestList | None = None) -> tuple[TestTuple, TestList
190190

191191
strip_py_suffix(tests)
192192

193+
exclude_tests = set()
194+
if self.exclude:
195+
for arg in self.cmdline_args:
196+
exclude_tests.add(arg)
197+
self.cmdline_args = []
198+
193199
if self.pgo:
194200
# add default PGO tests if no tests are specified
195201
setup_pgo_tests(self.cmdline_args, self.pgo_extended)
@@ -200,17 +206,15 @@ def find_tests(self, tests: TestList | None = None) -> tuple[TestTuple, TestList
200206
if self.tsan_parallel:
201207
setup_tsan_parallel_tests(self.cmdline_args)
202208

203-
exclude_tests = set()
204-
if self.exclude:
205-
for arg in self.cmdline_args:
206-
exclude_tests.add(arg)
207-
self.cmdline_args = []
208-
209209
alltests = findtests(testdir=self.test_dir,
210210
exclude=exclude_tests)
211211

212212
if not self.fromfile:
213213
selected = tests or self.cmdline_args
214+
if exclude_tests:
215+
# Support "--pgo/--tsan -x test_xxx" command
216+
selected = [name for name in selected
217+
if name not in exclude_tests]
214218
if selected:
215219
selected = split_test_packages(selected)
216220
else:

Lib/test/test_regrtest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,6 +2346,17 @@ def check(output):
23462346
output = self.run_tests('-j1', '-v', testname, env=env, isolated=False)
23472347
check(output)
23482348

2349+
def test_pgo_exclude(self):
2350+
# Get PGO tests
2351+
output = self.run_tests('--pgo', '--list-tests')
2352+
pgo_tests = output.strip().split()
2353+
2354+
# Exclude test_re
2355+
output = self.run_tests('--pgo', '--list-tests', '-x', 'test_re')
2356+
tests = output.strip().split()
2357+
self.assertNotIn('test_re', tests)
2358+
self.assertEqual(len(tests), len(pgo_tests) - 1)
2359+
23492360

23502361
class TestUtils(unittest.TestCase):
23512362
def test_format_duration(self):

Lib/test/test_reprlib.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,38 @@ def test_frozenset(self):
151151
eq(r(frozenset({1, 2, 3, 4, 5, 6, 7})), "frozenset({1, 2, 3, 4, 5, 6, ...})")
152152

153153
def test_numbers(self):
154-
eq = self.assertEqual
155-
eq(r(123), repr(123))
156-
eq(r(123), repr(123))
157-
eq(r(1.0/3), repr(1.0/3))
158-
159-
n = 10**100
160-
expected = repr(n)[:18] + "..." + repr(n)[-19:]
161-
eq(r(n), expected)
154+
for x in [123, 1.0 / 3]:
155+
self.assertEqual(r(x), repr(x))
156+
157+
max_digits = sys.get_int_max_str_digits()
158+
for k in [100, max_digits - 1]:
159+
with self.subTest(f'10 ** {k}', k=k):
160+
n = 10 ** k
161+
expected = repr(n)[:18] + "..." + repr(n)[-19:]
162+
self.assertEqual(r(n), expected)
163+
164+
def re_msg(n, d):
165+
return (rf'<{n.__class__.__name__} instance with roughly {d} '
166+
rf'digits \(limit at {max_digits}\) at 0x[a-f0-9]+>')
167+
168+
k = max_digits
169+
with self.subTest(f'10 ** {k}', k=k):
170+
n = 10 ** k
171+
self.assertRaises(ValueError, repr, n)
172+
self.assertRegex(r(n), re_msg(n, k + 1))
173+
174+
for k in [max_digits + 1, 2 * max_digits]:
175+
self.assertGreater(k, 100)
176+
with self.subTest(f'10 ** {k}', k=k):
177+
n = 10 ** k
178+
self.assertRaises(ValueError, repr, n)
179+
self.assertRegex(r(n), re_msg(n, k + 1))
180+
with self.subTest(f'10 ** {k} - 1', k=k):
181+
n = 10 ** k - 1
182+
# Here, since math.log10(n) == math.log10(n-1),
183+
# the number of digits of n - 1 is overestimated.
184+
self.assertRaises(ValueError, repr, n)
185+
self.assertRegex(r(n), re_msg(n, k + 1))
162186

163187
def test_instance(self):
164188
eq = self.assertEqual
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Restrict the trashcan mechanism to GC'ed objects and untrack them while in
2+
the trashcan to prevent the GC and trashcan mechanisms conflicting.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :meth:`!reprlib.Repr.repr_int` when given integers with more than
2+
:func:`sys.get_int_max_str_digits` digits. Patch by Bénédikt Tran.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix regrtest to support excluding tests from ``--pgo`` tests. Patch by
2+
Victor Stinner.

Objects/object.c

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,57 +3034,28 @@ Py_ReprLeave(PyObject *obj)
30343034

30353035
/* Trashcan support. */
30363036

3037-
#ifndef Py_GIL_DISABLED
3038-
/* We need to store a pointer in the refcount field of
3039-
* an object. It is important that we never store 0 (NULL).
3040-
* It is also important to not make the object appear immortal,
3041-
* or it might be untracked by the cycle GC. */
3042-
static uintptr_t
3043-
pointer_to_safe_refcount(void *ptr)
3044-
{
3045-
uintptr_t full = (uintptr_t)ptr;
3046-
assert((full & 3) == 0);
3047-
#if SIZEOF_VOID_P > 4
3048-
uint32_t refcnt = (uint32_t)full;
3049-
if (refcnt >= (uint32_t)_Py_IMMORTAL_MINIMUM_REFCNT) {
3050-
full = full - ((uintptr_t)_Py_IMMORTAL_MINIMUM_REFCNT) + 1;
3051-
}
3052-
return full + 2;
3053-
#else
3054-
// Make the top two bits 0, so it appears mortal.
3055-
return (full >> 2) + 1;
3056-
#endif
3057-
}
3058-
3059-
static void *
3060-
safe_refcount_to_pointer(uintptr_t refcnt)
3061-
{
3062-
#if SIZEOF_VOID_P > 4
3063-
if (refcnt & 1) {
3064-
refcnt += _Py_IMMORTAL_MINIMUM_REFCNT - 1;
3065-
}
3066-
return (void *)(refcnt - 2);
3067-
#else
3068-
return (void *)((refcnt -1) << 2);
3069-
#endif
3070-
}
3071-
#endif
3072-
30733037
/* Add op to the gcstate->trash_delete_later list. Called when the current
3074-
* call-stack depth gets large. op must be a currently untracked gc'ed
3075-
* object, with refcount 0. Py_DECREF must already have been called on it.
3038+
* call-stack depth gets large. op must be a gc'ed object, with refcount 0.
3039+
* Py_DECREF must already have been called on it.
30763040
*/
30773041
void
30783042
_PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op)
30793043
{
30803044
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
3045+
PyTypeObject *tp = Py_TYPE(op);
3046+
assert(tp->tp_flags & Py_TPFLAGS_HAVE_GC);
3047+
int tracked = 0;
3048+
if (tp->tp_is_gc == NULL || tp->tp_is_gc(op)) {
3049+
tracked = _PyObject_GC_IS_TRACKED(op);
3050+
if (tracked) {
3051+
_PyObject_GC_UNTRACK(op);
3052+
}
3053+
}
3054+
uintptr_t tagged_ptr = ((uintptr_t)tstate->delete_later) | tracked;
30813055
#ifdef Py_GIL_DISABLED
3082-
op->ob_tid = (uintptr_t)tstate->delete_later;
3056+
op->ob_tid = tagged_ptr;
30833057
#else
3084-
/* Store the delete_later pointer in the refcnt field. */
3085-
uintptr_t refcnt = pointer_to_safe_refcount(tstate->delete_later);
3086-
*((uintptr_t*)op) = refcnt;
3087-
assert(!_Py_IsImmortal(op));
3058+
_Py_AS_GC(op)->_gc_next = tagged_ptr;
30883059
#endif
30893060
tstate->delete_later = op;
30903061
}
@@ -3099,17 +3070,17 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate)
30993070
destructor dealloc = Py_TYPE(op)->tp_dealloc;
31003071

31013072
#ifdef Py_GIL_DISABLED
3102-
tstate->delete_later = (PyObject*) op->ob_tid;
3073+
uintptr_t tagged_ptr = op->ob_tid;
31033074
op->ob_tid = 0;
31043075
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED);
31053076
#else
3106-
/* Get the delete_later pointer from the refcnt field.
3107-
* See _PyTrash_thread_deposit_object(). */
3108-
uintptr_t refcnt = *((uintptr_t*)op);
3109-
tstate->delete_later = safe_refcount_to_pointer(refcnt);
3110-
op->ob_refcnt = 0;
3077+
uintptr_t tagged_ptr = _Py_AS_GC(op)->_gc_next;
3078+
_Py_AS_GC(op)->_gc_next = 0;
31113079
#endif
3112-
3080+
tstate->delete_later = (PyObject *)(tagged_ptr & ~1);
3081+
if (tagged_ptr & 1) {
3082+
_PyObject_GC_TRACK(op);
3083+
}
31133084
/* Call the deallocator directly. This used to try to
31143085
* fool Py_DECREF into calling it indirectly, but
31153086
* Py_DECREF was already called on this object, and in
@@ -3183,10 +3154,11 @@ void
31833154
_Py_Dealloc(PyObject *op)
31843155
{
31853156
PyTypeObject *type = Py_TYPE(op);
3157+
unsigned long gc_flag = type->tp_flags & Py_TPFLAGS_HAVE_GC;
31863158
destructor dealloc = type->tp_dealloc;
31873159
PyThreadState *tstate = _PyThreadState_GET();
31883160
intptr_t margin = _Py_RecursionLimit_GetMargin(tstate);
3189-
if (margin < 2) {
3161+
if (margin < 2 && gc_flag) {
31903162
_PyTrash_thread_deposit_object(tstate, (PyObject *)op);
31913163
return;
31923164
}
@@ -3232,7 +3204,7 @@ _Py_Dealloc(PyObject *op)
32323204
Py_XDECREF(old_exc);
32333205
Py_DECREF(type);
32343206
#endif
3235-
if (tstate->delete_later && margin >= 4) {
3207+
if (tstate->delete_later && margin >= 4 && gc_flag) {
32363208
_PyTrash_thread_destroy_chain(tstate);
32373209
}
32383210
}

0 commit comments

Comments
 (0)