Skip to content

Commit 16be8db

Browse files
authored
gh-123465: Allow Py_RELATIVE_OFFSET for __*offset__ members (GH-123474)
1 parent ce9f84a commit 16be8db

File tree

8 files changed

+422
-74
lines changed

8 files changed

+422
-74
lines changed

Doc/c-api/structures.rst

+8-1
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,8 @@ Accessing attributes of extension types
485485
``PyMemberDef`` may contain a definition for the special member
486486
``"__vectorcalloffset__"``, corresponding to
487487
:c:member:`~PyTypeObject.tp_vectorcall_offset` in type objects.
488-
These must be defined with ``Py_T_PYSSIZET`` and ``Py_READONLY``, for example::
488+
This member must be defined with ``Py_T_PYSSIZET``, and either
489+
``Py_READONLY`` or ``Py_READONLY | Py_RELATIVE_OFFSET``. For example::
489490
490491
static PyMemberDef spam_type_members[] = {
491492
{"__vectorcalloffset__", Py_T_PYSSIZET,
@@ -506,6 +507,12 @@ Accessing attributes of extension types
506507
``PyMemberDef`` is always available.
507508
Previously, it required including ``"structmember.h"``.
508509
510+
.. versionchanged:: 3.14
511+
512+
:c:macro:`Py_RELATIVE_OFFSET` is now allowed for
513+
``"__vectorcalloffset__"``, ``"__dictoffset__"`` and
514+
``"__weaklistoffset__"``.
515+
509516
.. c:function:: PyObject* PyMember_GetOne(const char *obj_addr, struct PyMemberDef *m)
510517
511518
Get an attribute belonging to the object at address *obj_addr*. The

Lib/test/test_call.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,13 @@ def get_a(x):
851851
@requires_limited_api
852852
def test_vectorcall_limited_incoming(self):
853853
from _testcapi import pyobject_vectorcall
854-
obj = _testlimitedcapi.LimitedVectorCallClass()
855-
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called")
854+
for cls in (_testlimitedcapi.LimitedVectorCallClass,
855+
_testlimitedcapi.LimitedRelativeVectorCallClass):
856+
with self.subTest(cls=cls):
857+
obj = cls()
858+
self.assertEqual(
859+
pyobject_vectorcall(obj, (), ()),
860+
"vectorcall called")
856861

857862
@requires_limited_api
858863
def test_vectorcall_limited_outgoing(self):

Lib/test/test_capi/test_misc.py

+109-41
Original file line numberDiff line numberDiff line change
@@ -541,14 +541,19 @@ def __del__(self):
541541
self.assertEqual(new_type_refcnt, sys.getrefcount(A))
542542

543543
def test_heaptype_with_dict(self):
544-
inst = _testcapi.HeapCTypeWithDict()
545-
inst.foo = 42
546-
self.assertEqual(inst.foo, 42)
547-
self.assertEqual(inst.dictobj, inst.__dict__)
548-
self.assertEqual(inst.dictobj, {"foo": 42})
544+
for cls in (
545+
_testcapi.HeapCTypeWithDict,
546+
_testlimitedcapi.HeapCTypeWithRelativeDict,
547+
):
548+
with self.subTest(cls=cls):
549+
inst = cls()
550+
inst.foo = 42
551+
self.assertEqual(inst.foo, 42)
552+
self.assertEqual(inst.dictobj, inst.__dict__)
553+
self.assertEqual(inst.dictobj, {"foo": 42})
549554

550-
inst = _testcapi.HeapCTypeWithDict()
551-
self.assertEqual({}, inst.__dict__)
555+
inst = cls()
556+
self.assertEqual({}, inst.__dict__)
552557

553558
def test_heaptype_with_managed_dict(self):
554559
inst = _testcapi.HeapCTypeWithManagedDict()
@@ -585,10 +590,15 @@ def test_heaptype_with_negative_dict(self):
585590
self.assertEqual({}, inst.__dict__)
586591

587592
def test_heaptype_with_weakref(self):
588-
inst = _testcapi.HeapCTypeWithWeakref()
589-
ref = weakref.ref(inst)
590-
self.assertEqual(ref(), inst)
591-
self.assertEqual(inst.weakreflist, ref)
593+
for cls in (
594+
_testcapi.HeapCTypeWithWeakref,
595+
_testlimitedcapi.HeapCTypeWithRelativeWeakref,
596+
):
597+
with self.subTest(cls=cls):
598+
inst = cls()
599+
ref = weakref.ref(inst)
600+
self.assertEqual(ref(), inst)
601+
self.assertEqual(inst.weakreflist, ref)
592602

593603
def test_heaptype_with_managed_weakref(self):
594604
inst = _testcapi.HeapCTypeWithManagedWeakref()
@@ -730,45 +740,56 @@ class Base(metaclass=metaclass):
730740
self.assertIsInstance(sub, metaclass)
731741

732742
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
743+
for weakref_cls in (_testcapi.HeapCTypeWithWeakref,
744+
_testlimitedcapi.HeapCTypeWithRelativeWeakref):
745+
for dict_cls in (_testcapi.HeapCTypeWithDict,
746+
_testlimitedcapi.HeapCTypeWithRelativeDict):
747+
with self.subTest(weakref_cls=weakref_cls, dict_cls=dict_cls):
733748

734-
with self.assertRaises(TypeError):
735-
class Both1(_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithDict):
736-
pass
737-
with self.assertRaises(TypeError):
738-
class Both2(_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithWeakref):
739-
pass
749+
with self.assertRaises(TypeError):
750+
class Both1(weakref_cls, dict_cls):
751+
pass
752+
with self.assertRaises(TypeError):
753+
class Both2(dict_cls, weakref_cls):
754+
pass
740755

741756
def test_multiple_inheritance_ctypes_with_weakref_or_dict_and_other_builtin(self):
757+
for dict_cls in (_testcapi.HeapCTypeWithDict,
758+
_testlimitedcapi.HeapCTypeWithRelativeDict):
759+
for weakref_cls in (_testcapi.HeapCTypeWithWeakref,
760+
_testlimitedcapi.HeapCTypeWithRelativeWeakref):
761+
with self.subTest(dict_cls=dict_cls, weakref_cls=weakref_cls):
742762

743-
with self.assertRaises(TypeError):
744-
class C1(_testcapi.HeapCTypeWithDict, list):
745-
pass
763+
with self.assertRaises(TypeError):
764+
class C1(dict_cls, list):
765+
pass
746766

747-
with self.assertRaises(TypeError):
748-
class C2(_testcapi.HeapCTypeWithWeakref, list):
749-
pass
767+
with self.assertRaises(TypeError):
768+
class C2(weakref_cls, list):
769+
pass
750770

751-
class C3(_testcapi.HeapCTypeWithManagedDict, list):
752-
pass
753-
class C4(_testcapi.HeapCTypeWithManagedWeakref, list):
754-
pass
771+
class C3(_testcapi.HeapCTypeWithManagedDict, list):
772+
pass
773+
class C4(_testcapi.HeapCTypeWithManagedWeakref, list):
774+
pass
755775

756-
inst = C3()
757-
inst.append(0)
758-
str(inst.__dict__)
776+
inst = C3()
777+
inst.append(0)
778+
str(inst.__dict__)
759779

760-
inst = C4()
761-
inst.append(0)
762-
str(inst.__weakref__)
780+
inst = C4()
781+
inst.append(0)
782+
str(inst.__weakref__)
763783

764-
for cls in (_testcapi.HeapCTypeWithManagedDict, _testcapi.HeapCTypeWithManagedWeakref):
765-
for cls2 in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithWeakref):
766-
class S(cls, cls2):
767-
pass
768-
class B1(C3, cls):
769-
pass
770-
class B2(C4, cls):
771-
pass
784+
for cls in (_testcapi.HeapCTypeWithManagedDict,
785+
_testcapi.HeapCTypeWithManagedWeakref):
786+
for cls2 in (dict_cls, weakref_cls):
787+
class S(cls, cls2):
788+
pass
789+
class B1(C3, cls):
790+
pass
791+
class B2(C4, cls):
792+
pass
772793

773794
def test_pytype_fromspec_with_repeated_slots(self):
774795
for variant in range(2):
@@ -1272,6 +1293,53 @@ def test_heaptype_relative_members_errors(self):
12721293
SystemError, r"PyMember_SetOne used with Py_RELATIVE_OFFSET"):
12731294
instance.set_memb_relative(0)
12741295

1296+
def test_heaptype_relative_special_members_errors(self):
1297+
for member_name in "__vectorcalloffset__", "__dictoffset__", "__weaklistoffset__":
1298+
with self.subTest(member_name=member_name):
1299+
with self.assertRaisesRegex(
1300+
SystemError,
1301+
r"With Py_RELATIVE_OFFSET, basicsize must be negative."):
1302+
_testlimitedcapi.make_heaptype_with_member(
1303+
basicsize=sys.getsizeof(object()) + 100,
1304+
add_relative_flag=True,
1305+
member_name=member_name,
1306+
member_offset=0,
1307+
member_type=_testlimitedcapi.Py_T_PYSSIZET,
1308+
member_flags=_testlimitedcapi.Py_READONLY,
1309+
)
1310+
with self.assertRaisesRegex(
1311+
SystemError,
1312+
r"Member offset out of range \(0\.\.-basicsize\)"):
1313+
_testlimitedcapi.make_heaptype_with_member(
1314+
basicsize=-8,
1315+
add_relative_flag=True,
1316+
member_name=member_name,
1317+
member_offset=-1,
1318+
member_type=_testlimitedcapi.Py_T_PYSSIZET,
1319+
member_flags=_testlimitedcapi.Py_READONLY,
1320+
)
1321+
with self.assertRaisesRegex(
1322+
SystemError,
1323+
r"type of %s must be Py_T_PYSSIZET" % member_name):
1324+
_testlimitedcapi.make_heaptype_with_member(
1325+
basicsize=-100,
1326+
add_relative_flag=True,
1327+
member_name=member_name,
1328+
member_offset=0,
1329+
member_flags=_testlimitedcapi.Py_READONLY,
1330+
)
1331+
with self.assertRaisesRegex(
1332+
SystemError,
1333+
r"flags for %s must be " % member_name):
1334+
_testlimitedcapi.make_heaptype_with_member(
1335+
basicsize=-100,
1336+
add_relative_flag=True,
1337+
member_name=member_name,
1338+
member_offset=0,
1339+
member_type=_testlimitedcapi.Py_T_PYSSIZET,
1340+
member_flags=0,
1341+
)
1342+
12751343
def test_pyobject_getitemdata_error(self):
12761344
"""Test PyObject_GetItemData fails on unsupported types"""
12771345
with self.assertRaises(TypeError):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:c:macro:`Py_RELATIVE_OFFSET` is now allowed in :c:type:`PyMemberDef` for
2+
the special offset member ``"__vectorcalloffset__"``, as well as the
3+
discouraged special offset members ``"__dictoffset__"`` and
4+
``"__weaklistoffset__"``

Modules/_testlimitedcapi/clinic/heaptype_relative.c.h

+44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)