8000 gh-123465: Allow Py_RELATIVE_OFFSET for __*offset__ members by encukou · Pull Request #123474 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-123465: Allow Py_RELATIVE_OFFSET for __*offset__ members #123474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Doc/c-api/structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,8 @@ Accessing attributes of extension types
``PyMemberDef`` may contain a definition for the special member
``"__vectorcalloffset__"``, corresponding to
:c:member:`~PyTypeObject.tp_vectorcall_offset` in type objects.
These must be defined with ``Py_T_PYSSIZET`` and ``Py_READONLY``, for example::
This member must be defined with ``Py_T_PYSSIZET``, and either
``Py_READONLY`` or ``Py_READONLY | Py_RELATIVE_OFFSET``. For example::

static PyMemberDef spam_type_members[] = {
{"__vectorcalloffset__", Py_T_PYSSIZET,
Expand All @@ -506,6 +507,12 @@ Accessing attributes of extension types
``PyMemberDef`` is always available.
Previously, it required including ``"structmember.h"``.

.. versionchanged:: 3.14

:c:macro:`Py_RELATIVE_OFFSET` is now allowed for
``"__vectorcalloffset__"``, ``"__dictoffset__"`` and
``"__weaklistoffset__"``.

.. c:function:: PyObject* PyMember_GetOne(const char *obj_addr, struct PyMemberDef *m)

Get an attribute belonging to the object at address *obj_addr*. The
Expand Down
9 changes: 7 additions & 2 deletions Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,8 +851,13 @@ def get_a(x):
@requires_limited_api
def test_vectorcall_limited_incoming(self):
from _testcapi import pyobject_vectorcall
obj = _testlimitedcapi.LimitedVectorCallClass()
self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called")
for cls in (_testlimitedcapi.LimitedVectorCallClass,
_testlimitedcapi.LimitedRelativeVectorCallClass):
with self.subTest(cls=cls):
obj = cls()
self.assertEqual(
pyobject_vectorcall(obj, (), ()),
"vectorcall called")

@requires_limited_api
def test_vectorcall_limited_outgoing(self):
Expand Down
150 changes: 109 additions & 41 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,14 +541,19 @@ def __del__(self):
self.assertEqual(new_type_refcnt, sys.getrefcount(A))

def test_heaptype_with_dict(self):
inst = _testcapi.HeapCTypeWithDict()
inst.foo = 42
self.assertEqual(inst.foo, 42)
self.assertEqual(inst.dictobj, inst.__dict__)
self.assertEqual(inst.dictobj, {"foo": 42})
for cls in (
_testcapi.HeapCTypeWithDict,
_testlimitedcapi.HeapCTypeWithRelativeDict,
):
with self.subTest(cls=cls):
inst = cls()
inst.foo = 42
self.assertEqual(inst.foo, 42)
self.assertEqual(inst.dictobj, inst.__dict__)
self.assertEqual(inst.dictobj, {"foo": 42})

inst = _testcapi.HeapCTypeWithDict()
self.assertEqual({}, inst.__dict__)
inst = cls()
self.assertEqual({}, inst.__dict__)

def test_heaptype_with_managed_dict(self):
inst = _testcapi.HeapCTypeWithManagedDict()
Expand Down Expand Up @@ -585,10 +590,15 @@ def test_heaptype_with_negative_dict(self):
self.assertEqual({}, inst.__dict__)

def test_heaptype_with_weakref(self):
inst = _testcapi.HeapCTypeWithWeakref()
ref = weakref.ref(inst)
self.assertEqual(ref(), inst)
self.assertEqual(inst.weakreflist, ref)
for cls in (
_testcapi.HeapCTypeWithWeakref,
_testlimitedcapi.HeapCTypeWithRelativeWeakref,
):
with self.subTest(cls=cls):
inst = cls()
ref = weakref.ref(inst)
self.assertEqual(ref(), inst)
self.assertEqual(inst.weakreflist, ref)

def test_heaptype_with_managed_weakref(self):
inst = _testcapi.HeapCTypeWithManagedWeakref()
Expand Down Expand Up @@ -730,45 +740,56 @@ class Base(metaclass=metaclass):
self.assertIsInstance(sub, metaclass)

def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
for weakref_cls in (_testcapi.HeapCTypeWithWeakref,
_testlimitedcapi.HeapCTypeWithRelativeWeakref):
for dict_cls in (_testcapi.HeapCTypeWithDict,
_testlimitedcapi.HeapCTypeWithRelativeDict):
with self.subTest(weakref_cls=weakref_cls, dict_cls=dict_cls):

with self.assertRaises(TypeError):
class Both1(_testcapi.HeapCTypeWithWeakref, _testcapi.HeapCTypeWithDict):
pass
with self.assertRaises(TypeError):
class Both2(_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithWeakref):
pass
with self.assertRaises(TypeError):
class Both1(weakref_cls, dict_cls):
pass
with self.assertRaises(TypeError):
class Both2(dict_cls, weakref_cls):
pass

def test_multiple_inheritance_ctypes_with_weakref_or_dict_and_other_builtin(self):
for dict_cls in (_testcapi.HeapCTypeWithDict,
_testlimitedcapi.HeapCTypeWithRelativeDict):
for weakref_cls in (_testcapi.HeapCTypeWithWeakref,
_testlimitedcapi.HeapCTypeWithRelativeWeakref):
with self.subTest(dict_cls=dict_cls, weakref_cls=weakref_cls):

with self.assertRaises(TypeError):
class C1(_testcapi.HeapCTypeWithDict, list):
pass
with self.assertRaises(TypeError):
class C1(dict_cls, list):
pass

with self.assertRaises(TypeError):
class C2(_testcapi.HeapCTypeWithWeakref, list):
pass
with self.assertRaises(TypeError):
class C2(weakref_cls, list):
pass

class C3(_testcapi.HeapCTypeWithManagedDict, list):
pass
class C4(_testcapi.HeapCTypeWithManagedWeakref, list):
pass
class C3(_testcapi.HeapCTypeWithManagedDict, list):
pass
class C4(_testcapi.HeapCTypeWithManagedWeakref, list):
pass

inst = C3()
inst.append(0)
str(inst.__dict__)
inst = C3()
inst.append(0)
str(inst.__dict__)

inst = C4()
inst.append(0)
str(inst.__weakref__)
inst = C4()
inst.append(0)
str(inst.__weakref__)

for cls in (_testcapi.HeapCTypeWithManagedDict, _testcapi.HeapCTypeWithManagedWeakref):
for cls2 in (_testcapi.HeapCTypeWithDict, _testcapi.HeapCTypeWithWeakref):
class S(cls, cls2):
pass
class B1(C3, cls):
pass
class B2(C4, cls):
pass
for cls in (_testcapi.HeapCTypeWithManagedDict,
_testcapi.HeapCTypeWithManagedWeakref):
for cls2 in (dict_cls, weakref_cls):
class S(cls, cls2):
pass
class B1(C3, cls):
pass
class B2(C4, cls):
pass

def test_pytype_fromspec_with_repeated_slots(self):
for variant in range(2):
Expand Down Expand Up @@ -1272,6 +1293,53 @@ def test_heaptype_relative_members_errors(self):
SystemError, r"PyMember_SetOne used with Py_RELATIVE_OFFSET"):
instance.set_memb_relative(0)

def test_heaptype_relative_special_members_errors(self):
for member_name in "__vectorcalloffset__", "__dictoffset__", "__weaklistoffset__":
with self.subTest(member_name=member_name):
with self.assertRaisesRegex(
SystemError,
r"With Py_RELATIVE_OFFSET, basicsize must be negative."):
_testlimitedcapi.make_heaptype_with_member(
basicsize=sys.getsizeof(object()) + 100,
add_relative_flag=True,
member_name=member_name,
member_offset=0,
member_type=_testlimitedcapi.Py_T_PYSSIZET,
member_flags=_testlimitedcapi.Py_READONLY,
)
with self.assertRaisesRegex(
SystemError,
r"Member offset out of range \(0\.\.-basicsize\)"):
_testlimitedcapi.make_heaptype_with_member(
basicsize=-8,
add_relative_flag=True,
member_name=member_name,
member_offset=-1,
member_type=_testlimitedcapi.Py_T_PYSSIZET,
member_flags=_testlimitedcapi.Py_READONLY,
)
with self.assertRaisesRegex(
SystemError,
r"type of %s must be Py_T_PYSSIZET" % member_name):
_testlimitedcapi.make_heaptype_with_member(
basicsize=-100,
add_relative_flag=True,
member_name=member_name,
member_offset=0,
member_flags=_testlimitedcapi.Py_READONLY,
)
with self.assertRaisesRegex(
SystemError,
r"flags for %s must be " % member_name):
_testlimitedcapi.make_heaptype_with_member(
basicsize=-100,
add_relative_flag=True,
member_name=member_name,
member_offset=0,
member_type=_testlimitedcapi.Py_T_PYSSIZET,
member_flags=0,
)

def test_pyobject_getitemdata_error(self):
"""Test PyObject_GetItemData fails on unsupported types"""
with self.assertRaises(TypeError):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:c:macro:`Py_RELATIVE_OFFSET` is now allowed in :c:type:`PyMemberDef` for
the special offset member ``"__vectorcalloffset__"``, as well as the
discouraged special offset members ``"__dictoffset__"`` and
``"__weaklistoffset__"``
44 changes: 44 additions & 0 deletions Modules/_testlimitedcapi/clinic/heaptype_relative.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading
0