Visitar URL original
gh-126220: Adapt `_lsprof` to Argument Clinic by sobolevn · Pull Request #126233 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
gh-126220: Fix crash on calls to _lsprof.Profiler methods with 0 args
  • Loading branch information
sobolevn committed Oct 31, 2024
commit 299f63f37f397c149a3067f4069903941529e376
16 changes: 16 additions & 0 deletions Lib/test/test_cprofile.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ def test_bad_counter_during_dealloc(self):

self.assertEqual(cm.unraisable.exc_type, TypeError)

def test_crash_on_no_args(self):
# gh-126220
import _lsprof

for profile in [_lsprof.Profiler(), cProfile.Profile()]:
for method in [
"_pystart_callback",
"_pyreturn_callback",
"_ccall_callback",
"_creturn_callback",
]:
with self.subTest(profile=profile, method=method):
method_obj = getattr(profile, method)
with self.assertRaises(TypeError):
method_obj() # should not crash

def test_evil_external_timer(self):
# gh-120289
# Disabling profiler in external timer should not crash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix crash of :class:`cProfile.Profile` and ``_lsprof.Profiler`` when their
callbacks were directly called with 0 arguments.
230 changes: 132 additions & 98 deletions Modules/_lsprof.c
Original file line number Diff line number Diff line change
Expand Up @@ -606,17 +606,41 @@ setBuiltins(ProfilerObject *pObj, int nvalue)
return 0;
}

PyObject* pystart_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
/*[clinic input]
_lsprof.Profiler._pystart_callback

code: object
obj: object
/

[clinic start generated code]*/

static PyObject *
_lsprof_Profiler__pystart_callback_impl(ProfilerObject *self, PyObject *code,
PyObject *obj)
/*[clinic end generated code: output=f6b04ac9658deb04 input=2a8a6a7b163e253d]*/
{
PyObject* code = args[0];
ptrace_enter_call((PyObject*)self, (void *)code, (PyObject *)code);
ptrace_enter_call((PyObject*)self, (void *)code, code);

Py_RETURN_NONE;
}

PyObject* pyreturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
/*[clinic input]
_lsprof.Profiler._pyreturn_callback

code: object
obj: object
retval: object
/

[clinic start generated code]*/

static PyObject *
_lsprof_Profiler__pyreturn_callback_impl(ProfilerObject *self,
PyObject *code, PyObject *obj,
PyObject *retval)
/*[clinic end generated code: output=dc0488deec84f7fc input=203c2cf434ae6ceb]*/
{
PyObject* code = args[0];
ptrace_leave_call((PyObject*)self, (void *)code);

Py_RETURN_NONE;
Expand Down Expand Up @@ -649,12 +673,24 @@ PyObject* get_cfunc_from_callable(PyObject* callable, PyObject* self_arg, PyObje
return NULL;
}

PyObject* ccall_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
/*[clinic input]
_lsprof.Profiler._ccall_callback

code: object
obj: object
callable: object
self_arg: object
/

[clinic start generated code]*/

static PyObject *
_lsprof_Profiler__ccall_callback_impl(ProfilerObject *self, PyObject *code,
PyObject *obj, PyObject *callable,
PyObject *self_arg)
/*[clinic end generated code: output=8d50bf59970d2a7e input=9b1560dce1c1a3c8]*/
{
if (self->flags & POF_BUILTINS) {
PyObject* callable = args[2];
PyObject* self_arg = args[3];

PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);

if (cfunc) {
Expand All @@ -667,12 +703,24 @@ PyObject* ccall_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t
Py_RETURN_NONE;
}

PyObject* creturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
/*[clinic input]
_lsprof.Profiler._creturn_callback

code: object
obj: object
callable: object
self_arg: object
/

[clinic start generated code]*/

static PyObject *
_lsprof_Profiler__creturn_callback_impl(ProfilerObject *self, PyObject *code,
PyObject *obj, PyObject *callable,
PyObject *self_arg)
/*[clinic end generated code: output=4c1f245bd3804b9a input=83e727bf01749202]*/
{
if (self->flags & POF_BUILTINS) {
PyObject* callable = args[2];
PyObject* self_arg = args[3];

PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);

if (cfunc) {
Expand Down Expand Up @@ -700,31 +748,27 @@ static const struct {
{0, NULL}
};

PyDoc_STRVAR(enable_doc, "\
enable(subcalls=True, builtins=True)\n\
\n\
Start collecting profiling information.\n\
If 'subcalls' is True, also records for each function\n\
statistics separated according to its current caller.\n\
If 'builtins' is True, records the time spent in\n\
built-in functions separately from their caller.\n\
");

static PyObject*
profiler_enable(ProfilerObject *self, PyObject *args, PyObject *kwds)
/*[clinic input]
_lsprof.Profiler.enable

subcalls: bool = True
builtins: bool = True

Start collecting profiling information.

If 'subcalls' is True, also records for each function
statistics separated according to its current caller.
If 'builtins' is True, records the time spent in
built-in functions separately from their caller.
Comment thread
sobolevn marked this conversation as resolved.
Outdated
[clinic start generated code]*/

static PyObject *
_lsprof_Profiler_enable_impl(ProfilerObject *self, int subcalls,
int builtins)
/*[clinic end generated code: output=1e747f9dc1edd571 input=0b6049b4e398781f]*/
{
int subcalls = -1;
int builtins = -1;
static char *kwlist[] = {"subcalls", "builtins", 0};
int all_events = 0;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "|pp:enable",
kwlist, &subcalls, &builtins))
return NULL;
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0) {
return NULL;
}

PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
if (!monitoring) {
return NULL;
Expand Down Expand Up @@ -776,14 +820,15 @@ flush_unmatched(ProfilerObject *pObj)

}

PyDoc_STRVAR(disable_doc, "\
disable()\n\
\n\
Stop collecting profiling information.\n\
");
/*[clinic input]
_lsprof.Profiler.disable

Stop collecting profiling information.
[clinic start generated code]*/

static PyObject*
profiler_disable(ProfilerObject *self, PyObject* noarg)
static PyObject *
_lsprof_Profiler_disable_impl(ProfilerObject *self)
/*[clinic end generated code: output=838cffef7f651870 input=05700b3fc68d1f50]*/
{
if (self->flags & POF_EXT_TIMER) {
PyErr_SetString(PyExc_RuntimeError,
Expand Down Expand Up @@ -834,21 +879,22 @@ profiler_disable(ProfilerObject *self, PyObject* noarg)
Py_RETURN_NONE;
}

PyDoc_STRVAR(clear_doc, "\
clear()\n\
\n\
Clear all profiling information collected so far.\n\
");
/*[clinic input]
_lsprof.Profiler.clear

Clear all profiling information collected so far.
[clinic start generated code]*/

static PyObject*
profiler_clear(ProfilerObject *pObj, PyObject* noarg)
static PyObject *
_lsprof_Profiler_clear_impl(ProfilerObject *self)
/*[clinic end generated code: output=dd1c668fb84b1335 input=fbe1f88c28be4f98]*/
{
if (pObj->flags & POF_EXT_TIMER) {
if (self->flags & POF_EXT_TIMER) {
PyErr_SetString(PyExc_RuntimeError,
"cannot clear profiler in external timer");
return NULL;
}
clearEntries(pObj);
clearEntries(self);
Py_RETURN_NONE;
}

Expand Down Expand Up @@ -879,74 +925,62 @@ profiler_dealloc(ProfilerObject *op)
Py_DECREF(tp);
}

/*[clinic input]
_lsprof.Profiler.__init__
Comment thread
sobolevn marked this conversation as resolved.
Outdated

timer: object(c_default='NULL') = None
timeunit: double = 0.0
subcalls: bool = True
builtins: bool = True

Builds a profiler object using the specified timer function.
Comment thread
sobolevn marked this conversation as resolved.
Outdated

The default timer is a fast built-in one based on real time.
For custom timer functions returning integers, timeunit can
Comment thread
sobolevn marked this conversation as resolved.
Outdated
be a float specifying a scale (i.e. how long each integer unit
Comment thread
sobolevn marked this conversation as resolved.
Outdated
is, in seconds).
[clinic start generated code]*/

static int
profiler_init(ProfilerObject *pObj, PyObject *args, PyObject *kw)
_lsprof_Profiler___init___impl(ProfilerObject *self, PyObject *timer,
double timeunit, int subcalls, int builtins)
/*[clinic end generated code: output=ab5498359fd34283 input=40225117dd22d4d7]*/
{
PyObject *timer = NULL;
double timeunit = 0.0;
int subcalls = 1;
int builtins = 1;
static char *kwlist[] = {"timer", "timeunit",
"subcalls", "builtins", 0};

if (!PyArg_ParseTupleAndKeywords(args, kw, "|Odpp:Profiler", kwlist,
&timer, &timeunit,
&subcalls, &builtins))
return -1;

if (setSubcalls(pObj, subcalls) < 0 || setBuiltins(pObj, builtins) < 0)
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0)
Comment thread
sobolevn marked this conversation as resolved.
Outdated
return -1;
pObj->externalTimerUnit = timeunit;
Py_XSETREF(pObj->externalTimer, Py_XNewRef(timer));
pObj->tool_id = PY_MONITORING_PROFILER_ID;
self->externalTimerUnit = timeunit;
Py_XSETREF(self->externalTimer, Py_XNewRef(timer));
self->tool_id = PY_MONITORING_PROFILER_ID;

PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
if (!monitoring) {
return -1;
}
pObj->missing = PyObject_GetAttrString(monitoring, "MISSING");
if (!pObj->missing) {
Py_DECREF(monitoring);
self->missing = PyObject_GetAttrString(monitoring, "MISSING");
Py_DECREF(monitoring);
if (!self->missing) {
return -1;
}
Py_DECREF(monitoring);
return 0;
}

static PyMethodDef profiler_methods[] = {
_LSPROF_PROFILER_GETSTATS_METHODDEF
{"enable", _PyCFunction_CAST(profiler_enable),
METH_VARARGS | METH_KEYWORDS, enable_doc},
{"disable", (PyCFunction)profiler_disable,
METH_NOARGS, disable_doc},
{"clear", (PyCFunction)profiler_clear,
METH_NOARGS, clear_doc},
{"_pystart_callback", _PyCFunction_CAST(pystart_callback),
METH_FASTCALL, NULL},
{"_pyreturn_callback", _PyCFunction_CAST(pyreturn_callback),
METH_FASTCALL, NULL},
{"_ccall_callback", _PyCFunction_CAST(ccall_callback),
METH_FASTCALL, NULL},
{"_creturn_callback", _PyCFunction_CAST(creturn_callback),
METH_FASTCALL, NULL},
_LSPROF_PROFILER_ENABLE_METHODDEF
_LSPROF_PROFILER_DISABLE_METHODDEF
_LSPROF_PROFILER_CLEAR_METHODDEF
_LSPROF_PROFILER__PYSTART_CALLBACK_METHODDEF
_LSPROF_PROFILER__PYRETURN_CALLBACK_METHODDEF
_LSPROF_PROFILER__CCALL_CALLBACK_METHODDEF
_LSPROF_PROFILER__CRETURN_CALLBACK_METHODDEF
{NULL, NULL}
};

PyDoc_STRVAR(profiler_doc, "\
Profiler(timer=None, timeunit=None, subcalls=True, builtins=True)\n\
\n\
Builds a profiler object using the specified timer function.\n\
The default timer is a fast built-in one based on real time.\n\
For custom timer functions returning integers, timeunit can\n\
be a float specifying a scale (i.e. how long each integer unit\n\
is, in seconds).\n\
");

static PyType_Slot _lsprof_profiler_type_spec_slots[] = {
{Py_tp_doc, (void *)profiler_doc},
{Py_tp_doc, (void *)_lsprof_Profiler___init____doc__},
{Py_tp_methods, profiler_methods},
{Py_tp_dealloc, profiler_dealloc},
{Py_tp_init, profiler_init},
{Py_tp_init, _lsprof_Profiler___init__},
{Py_tp_traverse, profiler_traverse},
{0, 0}
};
Expand Down
Loading