Skip to content

Commit aa8e4d5

Browse files
authored
Merge branch 'main' into gh-100240-freelist
2 parents a90b05f + 12c1afa commit aa8e4d5

33 files changed

+638
-331
lines changed

.github/workflows/reusable-tsan.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ jobs:
3636
# Install clang-18
3737
wget https://apt.llvm.org/llvm.sh
3838
chmod +x llvm.sh
39-
sudo ./llvm.sh 18
40-
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100
41-
sudo update-alternatives --set clang /usr/bin/clang-18
42-
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100
43-
sudo update-alternatives --set clang++ /usr/bin/clang++-18
39+
sudo ./llvm.sh 17 # gh-121946: llvm-18 package is temporarily broken
40+
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-17 100
41+
sudo update-alternatives --set clang /usr/bin/clang-17
42+
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-17 100
43+
sudo update-alternatives --set clang++ /usr/bin/clang++-17
4444
# Reduce ASLR to avoid TSAN crashing
4545
sudo sysctl -w vm.mmap_rnd_bits=28
4646
- name: TSAN Option Setup

Doc/library/functions.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1509,7 +1509,7 @@ are always available. They are listed here in alphabetical order.
15091509
(where :func:`open` is declared), :mod:`os`, :mod:`os.path`, :mod:`tempfile`,
15101510
and :mod:`shutil`.
15111511

1512-
.. audit-event:: open file,mode,flags open
1512+
.. audit-event:: open path,mode,flags open
15131513

15141514
The ``mode`` and ``flags`` arguments may have been modified or inferred from
15151515
the original call.

Doc/library/unittest.mock.rst

+14
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,20 @@ object::
860860
3
861861
>>> p.assert_called_once_with()
862862

863+
.. caution::
864+
865+
If an :exc:`AttributeError` is raised by :class:`PropertyMock`,
866+
it will be interpreted as a missing descriptor and
867+
:meth:`~object.__getattr__` will be called on the parent mock::
868+
869+
>>> m = MagicMock()
870+
>>> no_attribute = PropertyMock(side_effect=AttributeError)
871+
>>> type(m).my_property = no_attribute
872+
>>> m.my_property
873+
<MagicMock name='mock.my_property' id='140165240345424'>
874+
875+
See :meth:`~object.__getattr__` for details.
876+
863877

864878
.. class:: AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)
865879

Include/cpython/pystate.h

-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ struct _ts {
6868
pycore_ceval.h. */
6969
uintptr_t eval_breaker;
7070

71-
PyObject *asyncio_running_loop; // Strong reference
72-
7371
struct {
7472
/* Has been initialized to a safe state.
7573

Include/internal/pycore_frame.h

+25-20
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ typedef struct _PyInterpreterFrame {
6868
PyObject *f_locals; /* Strong reference, may be NULL. Only valid if not on C stack */
6969
PyFrameObject *frame_obj; /* Strong reference, may be NULL. Only valid if not on C stack */
7070
_Py_CODEUNIT *instr_ptr; /* Instruction currently executing (or about to begin) */
71-
int stacktop; /* Offset of TOS from localsplus */
71+
_PyStackRef *stackpointer;
7272
uint16_t return_offset; /* Only relevant during a function call */
7373
char owner;
7474
/* Locals and stack */
@@ -88,20 +88,20 @@ static inline _PyStackRef *_PyFrame_Stackbase(_PyInterpreterFrame *f) {
8888
}
8989

9090
static inline _PyStackRef _PyFrame_StackPeek(_PyInterpreterFrame *f) {
91-
assert(f->stacktop > _PyFrame_GetCode(f)->co_nlocalsplus);
92-
assert(!PyStackRef_IsNull(f->localsplus[f->stacktop-1]));
93-
return f->localsplus[f->stacktop-1];
91+
assert(f->stackpointer > f->localsplus + _PyFrame_GetCode(f)->co_nlocalsplus);
92+
assert(!PyStackRef_IsNull(f->stackpointer[-1]));
93+
return f->stackpointer[-1];
9494
}
9595

9696
static inline _PyStackRef _PyFrame_StackPop(_PyInterpreterFrame *f) {
97-
assert(f->stacktop > _PyFrame_GetCode(f)->co_nlocalsplus);
98-
f->stacktop--;
99-
return f->localsplus[f->stacktop];
97+
assert(f->stackpointer > f->localsplus + _PyFrame_GetCode(f)->co_nlocalsplus);
98+
f->stackpointer--;
99+
return *f->stackpointer;
100100
}
101101

102102
static inline void _PyFrame_StackPush(_PyInterpreterFrame *f, _PyStackRef value) {
103-
f->localsplus[f->stacktop] = value;
104-
f->stacktop++;
103+
*f->stackpointer = value;
104+
f->stackpointer++;
105105
}
106106

107107
#define FRAME_SPECIALS_SIZE ((int)((sizeof(_PyInterpreterFrame)-1)/sizeof(PyObject *)))
@@ -117,9 +117,12 @@ _PyFrame_NumSlotsForCodeObject(PyCodeObject *code)
117117

118118
static inline void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *dest)
119119
{
120-
assert(src->stacktop >= _PyFrame_GetCode(src)->co_nlocalsplus);
121120
*dest = *src;
122-
for (int i = 1; i < src->stacktop; i++) {
121+
assert(src->stackpointer != NULL);
122+
int stacktop = (int)(src->stackpointer - src->localsplus);
123+
assert(stacktop >= _PyFrame_GetCode(src)->co_nlocalsplus);
124+
dest->stackpointer = dest->localsplus + stacktop;
125+
for (int i = 1; i < stacktop; i++) {
123126
dest->localsplus[i] = src->localsplus[i];
124127
}
125128
// Don't leave a dangling pointer to the old frame when creating generators
@@ -141,7 +144,7 @@ _PyFrame_Initialize(
141144
frame->f_builtins = func->func_builtins;
142145
frame->f_globals = func->func_globals;
143146
frame->f_locals = locals;
144-
frame->stacktop = code->co_nlocalsplus;
147+
frame->stackpointer = frame->localsplus + code->co_nlocalsplus;
145148
frame->frame_obj = NULL;
146149
frame->instr_ptr = _PyCode_CODE(code);
147150
frame->return_offset = 0;
@@ -161,22 +164,23 @@ _PyFrame_GetLocalsArray(_PyInterpreterFrame *frame)
161164
return frame->localsplus;
162165
}
163166

164-
/* Fetches the stack pointer, and sets stacktop to -1.
165-
Having stacktop <= 0 ensures that invalid
166-
values are not visible to the cycle GC.
167-
We choose -1 rather than 0 to assist debugging. */
167+
/* Fetches the stack pointer, and sets stackpointer to NULL.
168+
Having stackpointer == NULL ensures that invalid
169+
values are not visible to the cycle GC. */
168170
static inline _PyStackRef*
169171
_PyFrame_GetStackPointer(_PyInterpreterFrame *frame)
170172
{
171-
_PyStackRef *sp = frame->localsplus + frame->stacktop;
172-
frame->stacktop = -1;
173+
assert(frame->stackpointer != NULL);
174+
_PyStackRef *sp = frame->stackpointer;
175+
frame->stackpointer = NULL;
173176
return sp;
174177
}
175178

176179
static inline void
177180
_PyFrame_SetStackPointer(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer)
178181
{
179-
frame->stacktop = (int)(stack_pointer - frame->localsplus);
182+
assert(frame->stackpointer == NULL);
183+
frame->stackpointer = stack_pointer;
180184
}
181185

182186
/* Determine whether a frame is incomplete.
@@ -304,7 +308,8 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
304308
frame->f_globals = NULL;
305309
#endif
306310
frame->f_locals = NULL;
307-
frame->stacktop = code->co_nlocalsplus + stackdepth;
311+
assert(stackdepth <= code->co_stacksize);
312+
frame->stackpointer = frame->localsplus + code->co_nlocalsplus + stackdepth;
308313
frame->frame_obj = NULL;
309314
frame->instr_ptr = _PyCode_CODE(code);
310315
frame->owner = FRAME_OWNED_BY_THREAD;

Include/internal/pycore_object.h

+26-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,30 @@ extern "C" {
1515
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_STORE_PTR_RELAXED
1616
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1717

18+
19+
#define _Py_IMMORTAL_REFCNT_LOOSE ((_Py_IMMORTAL_REFCNT >> 1) + 1)
20+
21+
// gh-121528, gh-118997: Similar to _Py_IsImmortal() but be more loose when
22+
// comparing the reference count to stay compatible with C extensions built
23+
// with the stable ABI 3.11 or older. Such extensions implement INCREF/DECREF
24+
// as refcnt++ and refcnt-- without taking in account immortal objects. For
25+
// example, the reference count of an immortal object can change from
26+
// _Py_IMMORTAL_REFCNT to _Py_IMMORTAL_REFCNT+1 (INCREF) or
27+
// _Py_IMMORTAL_REFCNT-1 (DECREF).
28+
//
29+
// This function should only be used in assertions. Otherwise, _Py_IsImmortal()
30+
// must be used instead.
31+
static inline int _Py_IsImmortalLoose(PyObject *op)
32+
{
33+
#if defined(Py_GIL_DISABLED)
34+
return _Py_IsImmortal(op);
35+
#else
36+
return (op->ob_refcnt >= _Py_IMMORTAL_REFCNT_LOOSE);
37+
#endif
38+
}
39+
#define _Py_IsImmortalLoose(op) _Py_IsImmortalLoose(_PyObject_CAST(op))
40+
41+
1842
/* Check if an object is consistent. For example, ensure that the reference
1943
counter is greater than or equal to 1, and ensure that ob_type is not NULL.
2044
@@ -134,7 +158,7 @@ PyAPI_FUNC(void) _Py_SetImmortalUntracked(PyObject *op);
134158
static inline void _Py_SetMortal(PyObject *op, Py_ssize_t refcnt)
135159
{
136160
if (op) {
137-
assert(_Py_IsImmortal(op));
161+
assert(_Py_IsImmortalLoose(op));
138162
#ifdef Py_GIL_DISABLED
139163
op->ob_tid = _Py_UNOWNED_TID;
140164
op->ob_ref_local = 0;
@@ -266,7 +290,7 @@ _PyObject_Init(PyObject *op, PyTypeObject *typeobj)
266290
{
267291
assert(op != NULL);
268292
Py_SET_TYPE(op, typeobj);
269-
assert(_PyType_HasFeature(typeobj, Py_TPFLAGS_HEAPTYPE) || _Py_IsImmortal(typeobj));
293+
assert(_PyType_HasFeature(typeobj, Py_TPFLAGS_HEAPTYPE) || _Py_IsImmortalLoose(typeobj));
270294
Py_INCREF(typeobj);
271295
_Py_NewReference(op);
272296
}

Include/internal/pycore_tstate.h

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ typedef struct _PyThreadStateImpl {
2121
// semi-public fields are in PyThreadState.
2222
PyThreadState base;
2323

24+
PyObject *asyncio_running_loop; // Strong reference
25+
2426
struct _qsbr_thread_state *qsbr; // only used by free-threaded build
2527
struct llist_node mem_free_queue; // delayed free queue
2628

Lib/email/_header_value_parser.py

+1
Original file line numberDiff line numberDiff line change
@@ -2988,6 +2988,7 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
29882988
excess = len(encoded_word) - remaining_space
29892989
lines[-1] += encoded_word
29902990
to_encode = to_encode[len(to_encode_word):]
2991+
leading_whitespace = ''
29912992

29922993
if to_encode:
29932994
lines.append(' ')

Lib/test/crashers/bogus_code_obj.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
1313
"""
1414

15-
import types
15+
def f():
16+
pass
1617

17-
co = types.CodeType(0, 0, 0, 0, 0, 0, b'\x04\x00\x71\x00',
18-
(), (), (), '', '', 1, b'')
19-
exec(co)
18+
f.__code__ = f.__code__.replace(co_code=b"")
19+
f()

Lib/test/test_email/test_generator.py

+13
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,19 @@ def test_defaults_handle_spaces_between_encoded_words_when_folded(self):
294294
g.flatten(msg)
295295
self.assertEqual(s.getvalue(), expected)
296296

297+
def test_defaults_handle_spaces_when_encoded_words_is_folded_in_middle(self):
298+
source = ('A very long long long long long long long long long long long long '
299+
'long long long long long long long long long long long súmmäry')
300+
expected = ('Subject: A very long long long long long long long long long long long long\n'
301+
' long long long long long long long long long long long =?utf-8?q?s=C3=BAmm?=\n'
302+
' =?utf-8?q?=C3=A4ry?=\n\n').encode('ascii')
303+
msg = EmailMessage()
304+
msg['Subject'] = source
305+
s = io.BytesIO()
306+
g = BytesGenerator(s)
307+
g.flatten(msg)
308+
self.assertEqual(s.getvalue(), expected)
309+
297310
def test_defaults_handle_spaces_at_start_of_subject(self):
298311
source = " Уведомление"
299312
expected = b"Subject: =?utf-8?b?0KPQstC10LTQvtC80LvQtdC90LjQtQ==?=\n\n"

0 commit comments

Comments
 (0)