Skip to content

Commit 8caa953

Browse files
author
Mohamed Koubaa
committed
Fix issue
1 parent 5a9913c commit 8caa953

2 files changed

Lines changed: 111 additions & 18 deletions

File tree

hpy/devel/src/runtime/ctx_type.c

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,39 @@ static int _decref_visitor(HPyField *pf, void *arg)
208208
return 0;
209209
}
210210

211-
static void hpytype_clear(PyObject *self)
211+
/* Python 3.13+ requires proper handling of Py_TPFLAGS_MANAGED_DICT.
212+
* Types that inherit from object get this flag, and must call
213+
* PyObject_VisitManagedDict in tp_traverse and PyObject_ClearManagedDict
214+
* in tp_clear/tp_dealloc. Without this, attribute lookup crashes.
215+
*/
216+
#if PY_VERSION_HEX >= 0x030D0000
217+
#define HPY_HAS_MANAGED_DICT 1
218+
#else
219+
#define HPY_HAS_MANAGED_DICT 0
220+
#endif
221+
222+
#if HPY_HAS_MANAGED_DICT
223+
/* Generic tp_traverse for HPy types that don't define their own HPy_tp_traverse.
224+
* This is needed on Python 3.13+ because we need to visit the managed dict.
225+
* For types WITH a user-defined traverse, the trampoline calls
226+
* call_traverseproc_from_trampoline which handles managed dict. */
227+
static int hpytype_traverse(PyObject *self, cpy_visitproc visit, void *arg)
228+
{
229+
/* On Python 3.13+, we must visit the managed dict */
230+
PyObject_VisitManagedDict(self, visit, arg);
231+
/* Visit the type object (required for heap types) */
232+
Py_VISIT(Py_TYPE(self));
233+
return 0;
234+
}
235+
#endif /* HPY_HAS_MANAGED_DICT */
236+
237+
static int hpytype_clear(PyObject *self)
212238
{
239+
#if HPY_HAS_MANAGED_DICT
240+
/* On Python 3.13+, we must clear the managed dict */
241+
PyObject_ClearManagedDict(self);
242+
#endif
243+
213244
// call tp_traverse on all the HPy types of the hierarchy
214245
PyTypeObject *tp = Py_TYPE(self);
215246
PyTypeObject *base = tp;
@@ -223,6 +254,7 @@ static void hpytype_clear(PyObject *self)
223254
}
224255
base = base->tp_base;
225256
}
257+
return 0;
226258
}
227259

228260
/* this is a generic tp_dealloc which we use for all the user-defined HPy
@@ -243,6 +275,13 @@ static void hpytype_dealloc(PyObject *self)
243275
// decref and clear all the HPyFields
244276
hpytype_clear(self);
245277

278+
#if HPY_HAS_MANAGED_DICT
279+
/* On Python 3.13+, ensure managed dict is cleared before dealloc.
280+
* Note: hpytype_clear already calls PyObject_ClearManagedDict, but
281+
* calling it again is safe and ensures proper cleanup. */
282+
PyObject_ClearManagedDict(self);
283+
#endif
284+
246285
// call tp_destroy on all the HPy types of the hierarchy
247286
PyTypeObject *base = tp;
248287
while(base) {
@@ -668,8 +707,18 @@ create_slot_defs(HPyType_Spec *hpyspec, HPyType_Extra_t *extra,
668707
hpyslot_count++; // Py_tp_getset
669708
if (needs_dealloc)
670709
hpyslot_count++; // Py_tp_dealloc
710+
#if HPY_HAS_MANAGED_DICT
711+
/* On Python 3.13+, we always need tp_traverse and tp_clear to handle
712+
* the managed dict properly, even for types without user-defined traverse.
713+
* If user provides HPy_tp_traverse, their trampoline handles managed dict.
714+
* If not, we add hpytype_traverse. Either way, we add hpytype_clear. */
715+
if (!has_tp_traverse(hpyspec))
716+
hpyslot_count++; // Py_tp_traverse (only if user didn't provide one)
717+
hpyslot_count++; // Py_tp_clear (always needed on 3.13+)
718+
#else
671719
if (has_tp_traverse(hpyspec))
672720
hpyslot_count++; // Py_tp_clear
721+
#endif
673722

674723
// allocate the result PyType_Slot array
675724
HPy_ssize_t total_slot_count = hpyslot_count + legacy_slot_count;
@@ -824,10 +873,26 @@ create_slot_defs(HPyType_Spec *hpyspec, HPyType_Extra_t *extra,
824873
result[dst_idx++] = (PyType_Slot){Py_tp_dealloc, (void*)hpytype_dealloc};
825874
}
826875

876+
#if HPY_HAS_MANAGED_DICT
877+
/* On Python 3.13+, we always need tp_traverse and tp_clear to handle
878+
* the managed dict properly. Py_TPFLAGS_MANAGED_DICT is inherited from
879+
* object, and requires calling PyObject_VisitManagedDict/ClearManagedDict.
880+
* If user provides HPy_tp_traverse, their trampoline handles managed dict
881+
* via call_traverseproc_from_trampoline. Otherwise, add hpytype_traverse.
882+
*
883+
* IMPORTANT: Py_TPFLAGS_MANAGED_DICT requires Py_TPFLAGS_HAVE_GC to be set.
884+
* Since managed dict is inherited from object, we must ensure HAVE_GC is set. */
885+
*flags |= Py_TPFLAGS_HAVE_GC;
886+
if (!has_tp_traverse(hpyspec)) {
887+
result[dst_idx++] = (PyType_Slot){Py_tp_traverse, (void*)hpytype_traverse};
888+
}
889+
result[dst_idx++] = (PyType_Slot){Py_tp_clear, (void*)hpytype_clear};
890+
#else
827891
// add a tp_clear, if the user provided a tp_traverse
828892
if (has_tp_traverse(hpyspec)) {
829893
result[dst_idx++] = (PyType_Slot){Py_tp_clear, (void*)hpytype_clear};
830894
}
895+
#endif
831896

832897
// add the NULL sentinel at the end
833898
result[dst_idx++] = (PyType_Slot){0, NULL};
@@ -956,6 +1021,13 @@ static bool has_tp_traverse(HPyType_Spec *hpyspec)
9561021

9571022
static bool needs_hpytype_dealloc(HPyType_Spec *hpyspec)
9581023
{
1024+
#if HPY_HAS_MANAGED_DICT
1025+
/* On Python 3.13+, we always need hpytype_dealloc because we set
1026+
* Py_TPFLAGS_HAVE_GC for proper managed dict handling. The dealloc
1027+
* must call PyObject_GC_UnTrack and PyObject_ClearManagedDict. */
1028+
(void)hpyspec; /* unused */
1029+
return true;
1030+
#else
9591031
if (hpyspec->defines != NULL)
9601032
for (int i = 0; hpyspec->defines[i] != NULL; i++) {
9611033
HPyDef *def = hpyspec->defines[i];
@@ -964,6 +1036,7 @@ static bool needs_hpytype_dealloc(HPyType_Spec *hpyspec)
9641036
return true;
9651037
}
9661038
return false;
1039+
#endif
9671040
}
9681041

9691042
static int check_have_gc_and_tp_traverse(HPyContext *ctx, HPyType_Spec *hpyspec)
@@ -1585,6 +1658,26 @@ _HPy_HIDDEN int call_traverseproc_from_trampoline(HPyFunc_traverseproc tp_traver
15851658
cpy_visitproc cpy_visit,
15861659
void *cpy_arg)
15871660
{
1661+
/* Visit the type object (required for heap types on Python 3.9+) */
1662+
#if PY_VERSION_HEX >= 0x03090000
1663+
{
1664+
PyObject *_py_type = (PyObject*)Py_TYPE(self);
1665+
int res = cpy_visit(_py_type, cpy_arg);
1666+
if (res)
1667+
return res;
1668+
}
1669+
#endif
1670+
1671+
#if HPY_HAS_MANAGED_DICT
1672+
/* On Python 3.13+, we must visit the managed dict */
1673+
{
1674+
int res = PyObject_VisitManagedDict(self, cpy_visit, cpy_arg);
1675+
if (res)
1676+
return res;
1677+
}
1678+
#endif
1679+
1680+
/* Now call the user's traverse implementation */
15881681
hpy2cpy_visit_args_t args = { cpy_visit, cpy_arg };
15891682
return tp_traverse(_pyobj_as_struct(self), hpy2cpy_visit, &args);
15901683
}

test/test_type_method_crash.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
class TestTypeMethodCrash(HPyTest):
1111
"""
1212
Reproducer for crash when calling methods on custom types in Universal ABI.
13-
13+
1414
The crash occurs when:
1515
1. A custom type is created via HPyType_FromSpec
1616
2. An instance is created via HPy_New
1717
3. A method (defined via HPyDef_METH) is called on the instance
18-
18+
1919
The crash happens before the method body is entered, suggesting an issue
2020
in the method dispatch mechanism for Universal ABI.
2121
"""
@@ -28,9 +28,9 @@ def test_simple_noargs_method(self):
2828
typedef struct {
2929
long value;
3030
} MyObject;
31-
31+
3232
HPyType_HELPERS(MyObject)
33-
33+
3434
HPyDef_SLOT(My_new, HPy_tp_new)
3535
static HPy My_new_impl(HPyContext *ctx, HPy cls, const HPy *args,
3636
HPy_ssize_t nargs, HPy kw)
@@ -42,26 +42,26 @@ def test_simple_noargs_method(self):
4242
obj->value = 42;
4343
return h;
4444
}
45-
45+
4646
HPyDef_METH(My_get_value, "get_value", HPyFunc_NOARGS)
4747
static HPy My_get_value_impl(HPyContext *ctx, HPy self)
4848
{
4949
MyObject *obj = MyObject_AsStruct(ctx, self);
5050
return HPyLong_FromLong(ctx, obj->value);
5151
}
52-
52+
5353
static HPyDef *My_defines[] = {
5454
&My_new,
5555
&My_get_value,
5656
NULL
5757
};
58-
58+
5959
static HPyType_Spec My_spec = {
6060
.name = "mytest.MyType",
6161
.basicsize = sizeof(MyObject),
6262
.defines = My_defines,
6363
};
64-
64+
6565
@EXPORT_TYPE("MyType", My_spec)
6666
@INIT
6767
""")
@@ -78,9 +78,9 @@ def test_simple_noargs_method_no_struct_access(self):
7878
typedef struct {
7979
long value;
8080
} MyObject;
81-
81+
8282
HPyType_HELPERS(MyObject)
83-
83+
8484
HPyDef_SLOT(My_new, HPy_tp_new)
8585
static HPy My_new_impl(HPyContext *ctx, HPy cls, const HPy *args,
8686
HPy_ssize_t nargs, HPy kw)
@@ -92,26 +92,26 @@ def test_simple_noargs_method_no_struct_access(self):
9292
obj->value = 42;
9393
return h;
9494
}
95-
95+
9696
HPyDef_METH(My_constant, "constant", HPyFunc_NOARGS)
9797
static HPy My_constant_impl(HPyContext *ctx, HPy self)
9898
{
9999
// Doesn't access struct at all - just returns a constant
100100
return HPyLong_FromLong(ctx, 123);
101101
}
102-
102+
103103
static HPyDef *My_defines[] = {
104104
&My_new,
105105
&My_constant,
106106
NULL
107107
};
108-
108+
109109
static HPyType_Spec My_spec = {
110110
.name = "mytest.MyType",
111111
.basicsize = sizeof(MyObject),
112112
.defines = My_defines,
113113
};
114-
114+
115115
@EXPORT_TYPE("MyType", My_spec)
116116
@INIT
117117
""")
@@ -130,17 +130,17 @@ def test_type_without_basicsize(self):
130130
{
131131
return HPyLong_FromLong(ctx, 456);
132132
}
133-
133+
134134
static HPyDef *My_defines[] = {
135135
&My_constant,
136136
NULL
137137
};
138-
138+
139139
static HPyType_Spec My_spec = {
140140
.name = "mytest.MyType",
141141
.defines = My_defines,
142142
};
143-
143+
144144
@EXPORT_TYPE("MyType", My_spec)
145145
@INIT
146146
""")

0 commit comments

Comments
 (0)