Skip to content

Commit fb1bc5e

Browse files
author
Joe Jevnik
committed
Manage memory lifetime for all type-related objects.
Remove internal usages of PyMethodDef and PyGetSetDef. For PyMethodDef, change PyCFunctionObject to replace the PyMethodDef* member with a PyCFunctionBase member. The PyCFunctionBase is a new struct to hold the managed values of a PyMethodDef. This type is shared between PyCFunction and the various callable descriptor objects. A PyCFunctionBase is like a PyMethodDef but replaces the char* members with PyObject* members. For PyGetSetDef, inline the members on the resulting PyGetSetDescrObject, replacing all char* members with PyObject* members. The memory for the closure is *not* managed, adding support for that would likely require an API change and can be done in a future change. For the tp_name field, instead of setting it directly to the value of PyType_Spec.name, set it to the result of PyUnicode_AsUTF8(ht_name), where ht_name is the PyUnicode object created from the original spec name. This is the same trick used to properly manage this pointer for heap types when the __name__ is reassigned.
1 parent fe2ad92 commit fb1bc5e

File tree

20 files changed

+547
-295
lines changed

20 files changed

+547
-295
lines changed

Doc/c-api/type.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ The following functions and structs are used to create
128128
129129
This function calls :c:func:`PyType_Ready` on the new type.
130130
131+
Until Python 3.9, the memory backing the ``Py_tp_methods``, ``Py_tp_getset``,
132+
and :c:member:`PyType_Spec.name` must outlive the returned type.
133+
131134
.. versionadded:: 3.3
132135
133136
.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)

Include/bltinmodule.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ PyAPI_DATA(PyTypeObject) PyFilter_Type;
88
PyAPI_DATA(PyTypeObject) PyMap_Type;
99
PyAPI_DATA(PyTypeObject) PyZip_Type;
1010

11+
#ifdef Py_BUILD_CORE
12+
PyObject* _PyBuiltin_Print(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames);
13+
#endif
14+
1115
#ifdef __cplusplus
1216
}
1317
#endif

Include/cpython/object.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,20 @@ typedef struct _heaptypeobject {
282282
PyBufferProcs as_buffer;
283283
PyObject *ht_name, *ht_slots, *ht_qualname;
284284
struct _dictkeysobject *ht_cached_keys;
285-
/* here are optional user slots, followed by the members. */
285+
/* here are optional user slots, followed by the offsets of the object members. */
286286
} PyHeapTypeObject;
287287

288-
/* access macro to the members which are floating "behind" the object */
289-
#define PyHeapType_GET_MEMBERS(etype) \
290-
((PyMemberDef *)(((char *)etype) + Py_TYPE(etype)->tp_basicsize))
288+
#ifdef Py_BUILD_CORE
289+
typedef struct {
290+
Py_ssize_t offset;
291+
int flags;
292+
} _PyObject_MemberSlot;
293+
294+
/* access macro to the offsets of the object type members which are floating
295+
"behind" the object */
296+
#define _PyHeapType_GET_OBJECT_MEMBER_OFFSETS(etype) \
297+
((_PyObject_MemberSlot *)(((char *)etype) + Py_TYPE(etype)->tp_basicsize))
298+
#endif
291299

292300
PyAPI_FUNC(const char *) _PyType_Name(PyTypeObject *);
293301
PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *);

Include/descrobject.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,24 @@ typedef struct {
5252

5353
typedef struct {
5454
PyDescr_COMMON;
55-
PyMethodDef *d_method;
55+
PyCFunctionBase d_base;
5656
vectorcallfunc vectorcall;
5757
} PyMethodDescrObject;
5858

5959
typedef struct {
6060
PyDescr_COMMON;
61-
struct PyMemberDef *d_member;
61+
int d_member_type;
62+
Py_ssize_t d_offset;
63+
int d_flags;
64+
PyObject *d_doc;
6265
} PyMemberDescrObject;
6366

6467
typedef struct {
6568
PyDescr_COMMON;
66-
PyGetSetDef *d_getset;
69+
getter d_get;
70+
setter d_set;
71+
PyObject *d_doc;
72+
void *d_closure;
6773
} PyGetSetDescrObject;
6874

6975
typedef struct {

Include/methodobject.h

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *);
3232
done, so use with care. */
3333
#ifndef Py_LIMITED_API
3434
#define PyCFunction_GET_FUNCTION(func) \
35-
(((PyCFunctionObject *)func) -> m_ml -> ml_meth)
35+
(((PyCFunctionObject *)func) -> m_base.meth)
3636
#define PyCFunction_GET_SELF(func) \
37-
(((PyCFunctionObject *)func) -> m_ml -> ml_flags & METH_STATIC ? \
37+
(((PyCFunctionObject *)func) -> m_base.flags & METH_STATIC ? \
3838
NULL : ((PyCFunctionObject *)func) -> m_self)
3939
#define PyCFunction_GET_FLAGS(func) \
40-
(((PyCFunctionObject *)func) -> m_ml -> ml_flags)
40+
(((PyCFunctionObject *)func) -> m_base.flags)
4141
#endif
4242
PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *);
4343

@@ -99,24 +99,36 @@ PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
9999
#endif
100100

101101
#ifndef Py_LIMITED_API
102+
typedef struct {
103+
PyObject *name;
104+
PyCFunction meth;
105+
PyObject *doc;
106+
PyObject *signature;
107+
int flags;
108+
} PyCFunctionBase;
109+
102110
typedef struct {
103111
PyObject_HEAD
104-
PyMethodDef *m_ml; /* Description of the C function to call */
105-
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
106-
PyObject *m_module; /* The __module__ attribute, can be anything */
107-
PyObject *m_weakreflist; /* List of weak references */
112+
PyCFunctionBase m_base;
113+
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
114+
PyObject *m_module; /* The __module__ attribute, can be anything */
115+
PyObject *m_weakreflist; /* List of weak references */
108116
vectorcallfunc vectorcall;
109117
} PyCFunctionObject;
110118

111-
PyAPI_FUNC(PyObject *) _PyMethodDef_RawFastCallDict(
112-
PyMethodDef *method,
119+
PyAPI_FUNC(int) _PyCFunctionBase_FromMethodDef(PyCFunctionBase *, PyMethodDef *);
120+
PyAPI_FUNC(void) _PyCFunctionBase_Clear(PyCFunctionBase *);
121+
122+
PyAPI_FUNC(PyObject *) _PyCFunction_NewFromBase(PyCFunctionBase *, PyObject *, PyObject *);
123+
PyAPI_FUNC(PyObject *) _PyCFunctionBase_RawFastCallDict(
124+
PyCFunctionBase *func,
113125
PyObject *self,
114126
PyObject *const *args,
115127
Py_ssize_t nargs,
116128
PyObject *kwargs);
117129

118-
PyAPI_FUNC(PyObject *) _PyMethodDef_RawFastCallKeywords(
119-
PyMethodDef *method,
130+
PyAPI_FUNC(PyObject *) _PyCFunctionBase_RawFastCallKeywords(
131+
PyCFunctionBase *func,
120132
PyObject *self,
121133
PyObject *const *args,
122134
Py_ssize_t nargs,

Include/structmember.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ typedef struct PyMemberDef {
6767
PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, struct PyMemberDef *);
6868
PyAPI_FUNC(int) PyMember_SetOne(char *, struct PyMemberDef *, PyObject *);
6969

70+
#ifdef Py_BUILD_CORE
71+
PyAPI_FUNC(PyObject *) _PyMemberDescr_GetOne(const char *, PyMemberDescrObject *);
72+
PyAPI_FUNC(int) _PyMemberDescr_SetOne(char *, PyMemberDescrObject *, PyObject *);
73+
#endif
74+
7075

7176
#ifdef __cplusplus
7277
}

Lib/test/test_cprofile.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -86,39 +86,39 @@ def main():
8686
8 0.312 0.039 0.400 0.050 profilee.py:88(helper2)
8787
8 0.064 0.008 0.080 0.010 profilee.py:98(subhelper)"""
8888
_ProfileOutput['print_callers'] = """\
89-
profilee.py:110(__getattr__) <- 16 0.016 0.016 profilee.py:98(subhelper)
90-
profilee.py:25(testfunc) <- 1 0.270 1.000 <string>:1(<module>)
91-
profilee.py:35(factorial) <- 1 0.014 0.130 profilee.py:25(testfunc)
92-
20/3 0.130 0.147 profilee.py:35(factorial)
93-
2 0.006 0.040 profilee.py:84(helper2_indirect)
94-
profilee.py:48(mul) <- 20 0.020 0.020 profilee.py:35(factorial)
95-
profilee.py:55(helper) <- 2 0.040 0.600 profilee.py:25(testfunc)
96-
profilee.py:73(helper1) <- 4 0.116 0.120 profilee.py:55(helper)
97-
profilee.py:84(helper2_indirect) <- 2 0.000 0.140 profilee.py:55(helper)
98-
profilee.py:88(helper2) <- 6 0.234 0.300 profilee.py:55(helper)
99-
2 0.078 0.100 profilee.py:84(helper2_indirect)
100-
profilee.py:98(subhelper) <- 8 0.064 0.080 profilee.py:88(helper2)
101-
{built-in method builtins.hasattr} <- 4 0.000 0.004 profilee.py:73(helper1)
102-
8 0.000 0.008 profilee.py:88(helper2)
103-
{built-in method sys.exc_info} <- 4 0.000 0.000 profilee.py:73(helper1)
104-
{method 'append' of 'list' objects} <- 4 0.000 0.000 profilee.py:73(helper1)"""
89+
profilee.py:110(__getattr__) <- 16 0.016 0.016 profilee.py:98(subhelper)
90+
profilee.py:25(testfunc) <- 1 0.270 1.000 <string>:1(<module>)
91+
profilee.py:35(factorial) <- 1 0.014 0.130 profilee.py:25(testfunc)
92+
20/3 0.130 0.147 profilee.py:35(factorial)
93+
2 0.006 0.040 profilee.py:84(helper2_indirect)
94+
profilee.py:48(mul) <- 20 0.020 0.020 profilee.py:35(factorial)
95+
profilee.py:55(helper) <- 2 0.040 0.600 profilee.py:25(testfunc)
96+
profilee.py:73(helper1) <- 4 0.116 0.120 profilee.py:55(helper)
97+
profilee.py:84(helper2_indirect) <- 2 0.000 0.140 profilee.py:55(helper)
98+
profilee.py:88(helper2) <- 6 0.234 0.300 profilee.py:55(helper)
99+
2 0.078 0.100 profilee.py:84(helper2_indirect)
100+
profilee.py:98(subhelper) <- 8 0.064 0.080 profilee.py:88(helper2)
101+
{built-in method builtins.hasattr} <- 4 0.000 0.004 profilee.py:73(helper1)
102+
8 0.000 0.008 profilee.py:88(helper2)
103+
{built-in method sys.exc_info} <- 4 0.000 0.000 profilee.py:73(helper1)
104+
{method 'append' of 'list' objects} <- 4 0.000 0.000 profilee.py:73(helper1)"""
105105
_ProfileOutput['print_callees'] = """\
106-
<string>:1(<module>) -> 1 0.270 1.000 profilee.py:25(testfunc)
107-
profilee.py:110(__getattr__) ->
108-
profilee.py:25(testfunc) -> 1 0.014 0.130 profilee.py:35(factorial)
109-
2 0.040 0.600 profilee.py:55(helper)
110-
profilee.py:35(factorial) -> 20/3 0.130 0.147 profilee.py:35(factorial)
111-
20 0.020 0.020 profilee.py:48(mul)
112-
profilee.py:48(mul) ->
113-
profilee.py:55(helper) -> 4 0.116 0.120 profilee.py:73(helper1)
114-
2 0.000 0.140 profilee.py:84(helper2_indirect)
115-
6 0.234 0.300 profilee.py:88(helper2)
116-
profilee.py:73(helper1) -> 4 0.000 0.004 {built-in method builtins.hasattr}
117-
profilee.py:84(helper2_indirect) -> 2 0.006 0.040 profilee.py:35(factorial)
118-
2 0.078 0.100 profilee.py:88(helper2)
119-
profilee.py:88(helper2) -> 8 0.064 0.080 profilee.py:98(subhelper)
120-
profilee.py:98(subhelper) -> 16 0.016 0.016 profilee.py:110(__getattr__)
121-
{built-in method builtins.hasattr} -> 12 0.012 0.012 profilee.py:110(__getattr__)"""
106+
<string>:1(<module>) -> 1 0.270 1.000 profilee.py:25(testfunc)
107+
profilee.py:110(__getattr__) ->
108+
profilee.py:25(testfunc) -> 1 0.014 0.130 profilee.py:35(factorial)
109+
2 0.040 0.600 profilee.py:55(helper)
110+
profilee.py:35(factorial) -> 20/3 0.130 0.147 profilee.py:35(factorial)
111+
20 0.020 0.020 profilee.py:48(mul)
112+
profilee.py:48(mul) ->
113+
profilee.py:55(helper) -> 4 0.116 0.120 profilee.py:73(helper1)
114+
2 0.000 0.140 profilee.py:84(helper2_indirect)
115+
6 0.234 0.300 profilee.py:88(helper2)
116+
profilee.py:73(helper1) -> 4 0.000 0.004 {built-in method builtins.hasattr}
117+
profilee.py:84(helper2_indirect) -> 2 0.006 0.040 profilee.py:35(factorial)
118+
2 0.078 0.100 profilee.py:88(helper2)
119+
profilee.py:88(helper2) -> 8 0.064 0.080 profilee.py:98(subhelper)
120+
profilee.py:98(subhelper) -> 16 0.016 0.016 profilee.py:110(__getattr__)
121+
{built-in method builtins.hasattr} -> 12 0.012 0.012 profilee.py:110(__getattr__)"""
122122

123123
if __name__ == "__main__":
124124
main()

Lib/test/test_sys.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ def test_objecttypes(self):
10801080
# buffer
10811081
# XXX
10821082
# builtin_function_or_method
1083-
check(len, size('5P'))
1083+
check(len, size('8Pi'))
10841084
# bytearray
10851085
samples = [b'', b'u'*100000]
10861086
for sample in samples:
@@ -1111,15 +1111,15 @@ def inner():
11111111
# complex
11121112
check(complex(0,1), size('2d'))
11131113
# method_descriptor (descriptor object)
1114-
check(str.lower, size('3PPP'))
1114+
check(str.lower, size('3P4PiP'))
11151115
# classmethod_descriptor (descriptor object)
11161116
# XXX
11171117
# member_descriptor (descriptor object)
11181118
import datetime
1119-
check(datetime.timedelta.days, size('3PP'))
1119+
check(datetime.timedelta.days, size('3PiniP'))
11201120
# getset_descriptor (descriptor object)
11211121
import collections
1122-
check(collections.defaultdict.default_factory, size('3PP'))
1122+
check(collections.defaultdict.default_factory, size('3P4P'))
11231123
# wrapper_descriptor (descriptor object)
11241124
check(int.__add__, size('3P2P'))
11251125
# method-wrapper (descriptor object)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Ensure that all of the memory needed by a type created with
2+
:c:func:`PyType_FromSpec` or :c:func:`PyType_FromSpecWithBases` is managed by
3+
Python. Previously, the memory referred to by ``Py_tp_methods``,
4+
``Py_tp_getset``, ``Py_tp_members`` and the various ``char*`` fields they
5+
contained needed to outlive the type. Now, these arrays and strings are copied
6+
and managed by the type itself.

Modules/_lsprof.c

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -132,42 +132,40 @@ normalizeUserObj(PyObject *obj)
132132
if (modname != NULL) {
133133
if (!_PyUnicode_EqualToASCIIString(modname, "builtins")) {
134134
PyObject *result;
135-
result = PyUnicode_FromFormat("<%U.%s>", modname,
136-
fn->m_ml->ml_name);
135+
result = PyUnicode_FromFormat("<%U.%S>", modname,
136+
fn->m_base.name);
137137
Py_DECREF(modname);
138138
return result;
139139
}
140140
Py_DECREF(modname);
141141
}
142-
return PyUnicode_FromFormat("<%s>", fn->m_ml->ml_name);
142+
return PyUnicode_FromFormat("<%S>", fn->m_base.name);
143143
}
144144
else {
145145
/* built-in method: try to return
146146
repr(getattr(type(__self__), __name__))
147147
*/
148148
PyObject *self = fn->m_self;
149-
PyObject *name = PyUnicode_FromString(fn->m_ml->ml_name);
149+
PyObject *name = fn->m_base.name;
150150
PyObject *modname = fn->m_module;
151151

152-
if (name != NULL) {
153-
PyObject *mo = _PyType_Lookup(Py_TYPE(self), name);
154-
Py_XINCREF(mo);
155-
Py_DECREF(name);
156-
if (mo != NULL) {
157-
PyObject *res = PyObject_Repr(mo);
158-
Py_DECREF(mo);
159-
if (res != NULL)
160-
return res;
161-
}
152+
PyObject *mo = _PyType_Lookup(Py_TYPE(self), name);
153+
Py_XINCREF(mo);
154+
if (mo != NULL) {
155+
PyObject *res = PyObject_Repr(mo);
156+
Py_DECREF(mo);
157+
if (res != NULL)
158+
return res;
162159
}
160+
163161
/* Otherwise, use __module__ */
164162
PyErr_Clear();
165163
if (modname != NULL && PyUnicode_Check(modname))
166-
return PyUnicode_FromFormat("<built-in method %S.%s>",
167-
modname, fn->m_ml->ml_name);
164+
return PyUnicode_FromFormat("<built-in method %S.%S>",
165+
modname, fn->m_base.name);
168166
else
169-
return PyUnicode_FromFormat("<built-in method %s>",
170-
fn->m_ml->ml_name);
167+
return PyUnicode_FromFormat("<built-in method %S>",
168+
fn->m_base.name);
171169
}
172170
}
173171

@@ -409,7 +407,7 @@ profiler_callback(PyObject *self, PyFrameObject *frame, int what,
409407
if ((((ProfilerObject *)self)->flags & POF_BUILTINS)
410408
&& PyCFunction_Check(arg)) {
411409
ptrace_enter_call(self,
412-
((PyCFunctionObject *)arg)->m_ml,
410+
&((PyCFunctionObject *)arg)->m_base,
413411
arg);
414412
}
415413
break;
@@ -421,7 +419,7 @@ profiler_callback(PyObject *self, PyFrameObject *frame, int what,
421419
if ((((ProfilerObject *)self)->flags & POF_BUILTINS)
422420
&& PyCFunction_Check(arg)) {
423421
ptrace_leave_call(self,
424-
((PyCFunctionObject *)arg)->m_ml);
422+
&((PyCFunctionObject *)arg)->m_base);
425423
}
426424
break;
427425

Objects/abstract.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <ctype.h>
66
#include "structmember.h" /* we need the offsetof() macro from there */
77
#include "longintrepr.h"
8+
#include "bltinmodule.h"
89

910

1011

@@ -840,7 +841,7 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
840841

841842
if (op_slot == NB_SLOT(nb_rshift) &&
842843
PyCFunction_Check(v) &&
843-
strcmp(((PyCFunctionObject *)v)->m_ml->ml_name, "print") == 0)
844+
PyCFunction_GET_FUNCTION(v) == (PyCFunction)_PyBuiltin_Print)
844845
{
845846
PyErr_Format(PyExc_TypeError,
846847
"unsupported operand type(s) for %.100s: "

0 commit comments

Comments
 (0)