Skip to content

Commit 12d9ac2

Browse files
Merge branch 'main' into fix-checck-html
2 parents 208a3a3 + 74a82a2 commit 12d9ac2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+335
-98
lines changed

Doc/c-api/module.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ Feature slots
230230
When creating a module, Python checks the value of this slot
231231
using :c:func:`PyABIInfo_Check`.
232232
233+
This slot is required, except for modules created from
234+
:c:struct:`PyModuleDef`.
235+
233236
.. versionadded:: 3.15
234237
235238
.. c:macro:: Py_mod_multiple_interpreters
@@ -620,9 +623,9 @@ rather than from an extension's :ref:`export hook <extension-export-hook>`.
620623
and the :py:class:`~importlib.machinery.ModuleSpec` *spec*.
621624
622625
The *slots* argument must point to an array of :c:type:`PyModuleDef_Slot`
623-
structures, terminated by an entry slot with slot ID of 0
626+
structures, terminated by an entry with slot ID of 0
624627
(typically written as ``{0}`` or ``{0, NULL}`` in C).
625-
The *slots* argument may not be ``NULL``.
628+
The array must include a :c:data:`Py_mod_abi` entry.
626629
627630
The *spec* argument may be any ``ModuleSpec``-like object, as described
628631
in :c:macro:`Py_mod_create` documentation.

Doc/extending/first-extension-module.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,19 @@ Define this array just before your export hook:
265265

266266
.. code-block:: c
267267
268+
PyABIInfo_VAR(abi_info);
269+
268270
static PyModuleDef_Slot spam_slots[] = {
271+
{Py_mod_abi, &abi_info},
269272
{Py_mod_name, "spam"},
270273
{Py_mod_doc, "A wonderful module with an example function"},
271274
{0, NULL}
272275
};
273276
277+
The ``PyABIInfo_VAR(abi_info);`` macro and the :c:data:`Py_mod_abi` slot
278+
are a bit of boilerplate that helps prevent extensions compiled for
279+
a different version of Python from crashing the interpreter.
280+
274281
For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C
275282
strings -- that is, NUL-terminated, UTF-8 encoded byte arrays.
276283

Doc/includes/capi-extension/spammodule-01.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ static PyMethodDef spam_methods[] = {
3535

3636
/// Module slot table
3737

38+
PyABIInfo_VAR(abi_info);
39+
3840
static PyModuleDef_Slot spam_slots[] = {
41+
{Py_mod_abi, &abi_info},
3942
{Py_mod_name, "spam"},
4043
{Py_mod_doc, "A wonderful module with an example function"},
4144
{Py_mod_methods, spam_methods},

Doc/library/functions.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1754,7 +1754,7 @@ are always available. They are listed here in alphabetical order.
17541754
self.age = age
17551755

17561756
def __repr__(self):
1757-
return f"Person('{self.name}', {self.age})"
1757+
return f"Person({self.name!r}, {self.age!r})"
17581758

17591759

17601760
.. function:: reversed(object, /)

Doc/whatsnew/3.15.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,11 +1347,11 @@ Upgraded JIT compiler
13471347

13481348
Results from the `pyperformance <https://github.com/python/pyperformance>`__
13491349
benchmark suite report
1350-
`5-6% <https://doesjitgobrrr.com/run/2026-03-11>`__
1350+
`6-7% <https://www.doesjitgobrrr.com/run/2026-04-01>`__
13511351
geometric mean performance improvement for the JIT over the standard CPython
13521352
interpreter built with all optimizations enabled on x86-64 Linux. On AArch64
13531353
macOS, the JIT has a
1354-
`8-9% <https://doesjitgobrrr.com/run/2026-03-11>`__
1354+
`12-13% <https://www.doesjitgobrrr.com/run/2026-04-01>`__
13551355
speedup over the :ref:`tail calling interpreter <whatsnew314-tail-call-interpreter>`
13561356
with all optimizations enabled. The speedups for JIT
13571357
builds versus no JIT builds range from roughly 15% slowdown to over

Include/cpython/longintrepr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ _PyLong_CompactValue(const PyLongObject *op)
133133
assert(PyType_HasFeature(op->ob_base.ob_type, Py_TPFLAGS_LONG_SUBCLASS));
134134
assert(PyUnstable_Long_IsCompact(op));
135135
sign = 1 - (op->long_value.lv_tag & _PyLong_SIGN_MASK);
136+
if (sign == 0) {
137+
// gh-147988: Make sure that the digit is zero.
138+
// It helps detecting the usage of uninitialized digits.
139+
assert(op->long_value.ob_digit[0] == 0);
140+
}
136141
return sign * (Py_ssize_t)op->long_value.ob_digit[0];
137142
}
138143

Include/internal/pycore_long.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,12 @@ _PyLong_IsSmallInt(const PyLongObject *op)
238238
{
239239
assert(PyLong_Check(op));
240240
bool is_small_int = (op->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
241-
assert(PyLong_CheckExact(op) || (!is_small_int));
242-
assert(_Py_IsImmortal(op) || (!is_small_int));
243-
assert((_PyLong_IsCompact(op)
244-
&& _PY_IS_SMALL_INT(_PyLong_CompactValue(op)))
245-
|| (!is_small_int));
241+
if (is_small_int) {
242+
assert(PyLong_CheckExact(op));
243+
assert(_Py_IsImmortal(op));
244+
assert((_PyLong_IsCompact(op)
245+
&& _PY_IS_SMALL_INT(_PyLong_CompactValue(op))));
246+
}
246247
return is_small_int;
247248
}
248249

@@ -285,6 +286,14 @@ _PyLong_SameSign(const PyLongObject *a, const PyLongObject *b)
285286
return (a->long_value.lv_tag & SIGN_MASK) == (b->long_value.lv_tag & SIGN_MASK);
286287
}
287288

289+
/* Initialize the tag of a freshly-allocated int. */
290+
static inline void
291+
_PyLong_InitTag(PyLongObject *op)
292+
{
293+
assert(PyLong_Check(op));
294+
op->long_value.lv_tag = SIGN_ZERO; /* non-immortal zero */
295+
}
296+
288297
#define TAG_FROM_SIGN_AND_SIZE(sign, size) \
289298
((uintptr_t)(1 - (sign)) | ((uintptr_t)(size) << NON_SIZE_BITS))
290299

@@ -294,13 +303,15 @@ _PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size)
294303
assert(size >= 0);
295304
assert(-1 <= sign && sign <= 1);
296305
assert(sign != 0 || size == 0);
306+
assert(!_PyLong_IsSmallInt(op));
297307
op->long_value.lv_tag = TAG_FROM_SIGN_AND_SIZE(sign, size);
298308
}
299309

300310
static inline void
301311
_PyLong_SetDigitCount(PyLongObject *op, Py_ssize_t size)
302312
{
303313
assert(size >= 0);
314+
assert(!_PyLong_IsSmallInt(op));
304315
op->long_value.lv_tag = (((size_t)size) << NON_SIZE_BITS) | (op->long_value.lv_tag & SIGN_MASK);
305316
}
306317

Lib/test/support/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
"BrokenIter",
7272
"in_systemd_nspawn_sync_suppressed",
7373
"run_no_yield_async_fn", "run_yielding_async_fn", "async_yield",
74-
"reset_code", "on_github_actions"
74+
"reset_code", "on_github_actions",
75+
"requires_root_user", "requires_non_root_user",
7576
]
7677

7778

@@ -3317,3 +3318,8 @@ def control_characters_c0() -> list[str]:
33173318
C0 control characters defined as the byte range 0x00-0x1F, and 0x7F.
33183319
"""
33193320
return [chr(c) for c in range(0x00, 0x20)] + ["\x7F"]
3321+
3322+
3323+
_ROOT_IN_POSIX = hasattr(os, 'geteuid') and os.geteuid() == 0
3324+
requires_root_user = unittest.skipUnless(_ROOT_IN_POSIX, "test needs root privilege")
3325+
requires_non_root_user = unittest.skipIf(_ROOT_IN_POSIX, "test needs non-root account")

Lib/test/test_capi/test_long.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,16 @@ def to_digits(num):
803803
self.assertEqual(pylongwriter_create(negative, digits), num,
804804
(negative, digits))
805805

806+
@unittest.skipUnless(support.Py_DEBUG, "need a debug build (Py_DEBUG)")
807+
def test_longwriter_finish(self):
808+
# Test PyLongWriter_Create(0, 3, &digits) with PyLongWriter_Finish()
809+
# where the last digit is left uninitialized
810+
pylongwriter_finish_bug = _testcapi.pylongwriter_finish_bug
811+
with self.assertRaises(SystemError) as cm:
812+
pylongwriter_finish_bug()
813+
self.assertEqual(str(cm.exception),
814+
'PyLongWriter_Finish: digit 2 is uninitialized')
815+
806816
def test_bug_143050(self):
807817
with support.adjust_int_max_str_digits(0):
808818
# Bug coming from using _pylong.int_from_string(), that

Lib/test/test_capi/test_module.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ def def_and_token(mod):
2525
)
2626

2727
class TestModFromSlotsAndSpec(unittest.TestCase):
28-
@requires_gil_enabled("empty slots re-enable GIL")
2928
def test_empty(self):
30-
mod = _testcapi.module_from_slots_empty(FakeSpec())
29+
with self.assertRaises(SystemError):
30+
_testcapi.module_from_slots_empty(FakeSpec())
31+
32+
@requires_gil_enabled("minimal slots re-enable GIL")
33+
def test_minimal(self):
34+
mod = _testcapi.module_from_slots_minimal(FakeSpec())
3135
self.assertIsInstance(mod, types.ModuleType)
3236
self.assertEqual(def_and_token(mod), (0, 0))
3337
self.assertEqual(mod.__name__, 'testmod')
@@ -159,6 +163,16 @@ def test_null_def_slot(self):
159163
self.assertIn(name, str(cm.exception))
160164
self.assertIn("NULL", str(cm.exception))
161165

166+
def test_bad_abiinfo(self):
167+
"""Slots that incompatible ABI is rejected"""
168+
with self.assertRaises(ImportError) as cm:
169+
_testcapi.module_from_bad_abiinfo(FakeSpec())
170+
171+
def test_multiple_abiinfo(self):
172+
"""Slots that Py_mod_abiinfo can be repeated"""
173+
mod = _testcapi.module_from_multiple_abiinfo(FakeSpec())
174+
self.assertEqual(mod.__name__, 'testmod')
175+
162176
def test_def_multiple_exec(self):
163177
"""PyModule_Exec runs all exec slots of PyModuleDef-defined module"""
164178
mod = _testcapi.module_from_def_multiple_exec(FakeSpec())

0 commit comments

Comments
 (0)