Skip to content

Commit a1b9465

Browse files
authored
Merge branch 'main' into tests-root-improvements
2 parents d569149 + 9b08f8c commit a1b9465

38 files changed

+313
-75
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, /)

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/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())

Lib/test/test_cext/extension.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ _Py_COMP_DIAG_PUSH
119119
#endif
120120

121121
PyDoc_STRVAR(_testcext_doc, "C test extension.");
122+
PyABIInfo_VAR(abi_info);
122123

123124
static PyModuleDef_Slot _testcext_slots[] = {
125+
{Py_mod_abi, &abi_info},
124126
{Py_mod_name, STR(MODULE_NAME)},
125127
{Py_mod_doc, (void*)(char*)_testcext_doc},
126128
{Py_mod_exec, (void*)_testcext_exec},

Lib/test/test_import/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3499,12 +3499,20 @@ class Sub(tp):
34993499
pass
35003500
self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module)
35013501

3502-
@requires_gil_enabled("empty slots re-enable GIL")
35033502
def test_from_modexport_empty_slots(self):
3503+
# Module to test that Py_mod_abi is mandatory for PyModExport
3504+
modname = '_test_from_modexport_empty_slots'
3505+
filename = _testmultiphase.__file__
3506+
with self.assertRaises(SystemError):
3507+
import_extension_from_file(
3508+
modname, filename, put_in_sys_modules=False)
3509+
3510+
@requires_gil_enabled("this module re-enables GIL")
3511+
def test_from_modexport_minimal_slots(self):
35043512
# Module to test that:
3505-
# - no slots are mandatory for PyModExport
3513+
# - no slots except Py_mod_abi is mandatory for PyModExport
35063514
# - the slots array is used as the default token
3507-
modname = '_test_from_modexport_empty_slots'
3515+
modname = '_test_from_modexport_minimal_slots'
35083516
filename = _testmultiphase.__file__
35093517
module = import_extension_from_file(
35103518
modname, filename, put_in_sys_modules=False)
@@ -3516,7 +3524,7 @@ def test_from_modexport_empty_slots(self):
35163524
smoke_mod = import_extension_from_file(
35173525
'_test_from_modexport_smoke', filename, put_in_sys_modules=False)
35183526
self.assertEqual(_testcapi.pymodule_get_token(module),
3519-
smoke_mod.get_modexport_empty_slots())
3527+
smoke_mod.get_modexport_minimal_slots())
35203528

35213529
@cpython_only
35223530
class TestMagicNumber(unittest.TestCase):

0 commit comments

Comments
 (0)