Skip to content

Commit 299f63f

Browse files
committed
gh-126220: Fix crash on calls to _lsprof.Profiler methods with 0 args
1 parent d07dcce commit 299f63f

File tree

4 files changed

+506
-99
lines changed

4 files changed

+506
-99
lines changed

Lib/test/test_cprofile.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ def test_bad_counter_during_dealloc(self):
3030

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

33+
def test_crash_on_no_args(self):
34+
# gh-126220
35+
import _lsprof
36+
37+
for profile in [_lsprof.Profiler(), cProfile.Profile()]:
38+
for method in [
39+
"_pystart_callback",
40+
"_pyreturn_callback",
41+
"_ccall_callback",
42+
"_creturn_callback",
43+
]:
44+
with self.subTest(profile=profile, method=method):
45+
method_obj = getattr(profile, method)
46+
with self.assertRaises(TypeError):
47+
method_obj() # should not crash
48+
3349
def test_evil_external_timer(self):
3450
# gh-120289
3551
# Disabling profiler in external timer should not crash
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash of :class:`cProfile.Profile` and ``_lsprof.Profiler`` when their
2+
callbacks were directly called with 0 arguments.

Modules/_lsprof.c

Lines changed: 132 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -606,17 +606,41 @@ setBuiltins(ProfilerObject *pObj, int nvalue)
606606
return 0;
607607
}
608608

609-
PyObject* pystart_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
609+
/*[clinic input]
610+
_lsprof.Profiler._pystart_callback
611+
612+
code: object
613+
obj: object
614+
/
615+
616+
[clinic start generated code]*/
617+
618+
static PyObject *
619+
_lsprof_Profiler__pystart_callback_impl(ProfilerObject *self, PyObject *code,
620+
PyObject *obj)
621+
/*[clinic end generated code: output=f6b04ac9658deb04 input=2a8a6a7b163e253d]*/
610622
{
611-
PyObject* code = args[0];
612-
ptrace_enter_call((PyObject*)self, (void *)code, (PyObject *)code);
623+
ptrace_enter_call((PyObject*)self, (void *)code, code);
613624

614625
Py_RETURN_NONE;
615626
}
616627

617-
PyObject* pyreturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
628+
/*[clinic input]
629+
_lsprof.Profiler._pyreturn_callback
630+
631+
code: object
632+
obj: object
633+
retval: object
634+
/
635+
636+
[clinic start generated code]*/
637+
638+
static PyObject *
639+
_lsprof_Profiler__pyreturn_callback_impl(ProfilerObject *self,
640+
PyObject *code, PyObject *obj,
641+
PyObject *retval)
642+
/*[clinic end generated code: output=dc0488deec84f7fc input=203c2cf434ae6ceb]*/
618643
{
619-
PyObject* code = args[0];
620644
ptrace_leave_call((PyObject*)self, (void *)code);
621645

622646
Py_RETURN_NONE;
@@ -649,12 +673,24 @@ PyObject* get_cfunc_from_callable(PyObject* callable, PyObject* self_arg, PyObje
649673
return NULL;
650674
}
651675

652-
PyObject* ccall_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
676+
/*[clinic input]
677+
_lsprof.Profiler._ccall_callback
678+
679+
code: object
680+
obj: object
681+
callable: object
682+
self_arg: object
683+
/
684+
685+
[clinic start generated code]*/
686+
687+
static PyObject *
688+
_lsprof_Profiler__ccall_callback_impl(ProfilerObject *self, PyObject *code,
689+
PyObject *obj, PyObject *callable,
690+
PyObject *self_arg)
691+
/*[clinic end generated code: output=8d50bf59970d2a7e input=9b1560dce1c1a3c8]*/
653692
{
654693
if (self->flags & POF_BUILTINS) {
655-
PyObject* callable = args[2];
656-
PyObject* self_arg = args[3];
657-
658694
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
659695

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

670-
PyObject* creturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
706+
/*[clinic input]
707+
_lsprof.Profiler._creturn_callback
708+
709+
code: object
710+
obj: object
711+
callable: object
712+
self_arg: object
713+
/
714+
715+
[clinic start generated code]*/
716+
717+
static PyObject *
718+
_lsprof_Profiler__creturn_callback_impl(ProfilerObject *self, PyObject *code,
719+
PyObject *obj, PyObject *callable,
720+
PyObject *self_arg)
721+
/*[clinic end generated code: output=4c1f245bd3804b9a input=83e727bf01749202]*/
671722
{
672723
if (self->flags & POF_BUILTINS) {
673-
PyObject* callable = args[2];
674-
PyObject* self_arg = args[3];
675-
676724
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
677725

678726
if (cfunc) {
@@ -700,31 +748,27 @@ static const struct {
700748
{0, NULL}
701749
};
702750

703-
PyDoc_STRVAR(enable_doc, "\
704-
enable(subcalls=True, builtins=True)\n\
705-
\n\
706-
Start collecting profiling information.\n\
707-
If 'subcalls' is True, also records for each function\n\
708-
statistics separated according to its current caller.\n\
709-
If 'builtins' is True, records the time spent in\n\
710-
built-in functions separately from their caller.\n\
711-
");
712-
713-
static PyObject*
714-
profiler_enable(ProfilerObject *self, PyObject *args, PyObject *kwds)
751+
/*[clinic input]
752+
_lsprof.Profiler.enable
753+
754+
subcalls: bool = True
755+
builtins: bool = True
756+
757+
Start collecting profiling information.
758+
759+
If 'subcalls' is True, also records for each function
760+
statistics separated according to its current caller.
761+
If 'builtins' is True, records the time spent in
762+
built-in functions separately from their caller.
763+
[clinic start generated code]*/
764+
765+
static PyObject *
766+
_lsprof_Profiler_enable_impl(ProfilerObject *self, int subcalls,
767+
int builtins)
768+
/*[clinic end generated code: output=1e747f9dc1edd571 input=0b6049b4e398781f]*/
715769
{
716-
int subcalls = -1;
717-
int builtins = -1;
718-
static char *kwlist[] = {"subcalls", "builtins", 0};
719770
int all_events = 0;
720771

721-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|pp:enable",
722-
kwlist, &subcalls, &builtins))
723-
return NULL;
724-
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0) {
725-
return NULL;
726-
}
727-
728772
PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
729773
if (!monitoring) {
730774
return NULL;
@@ -776,14 +820,15 @@ flush_unmatched(ProfilerObject *pObj)
776820

777821
}
778822

779-
PyDoc_STRVAR(disable_doc, "\
780-
disable()\n\
781-
\n\
782-
Stop collecting profiling information.\n\
783-
");
823+
/*[clinic input]
824+
_lsprof.Profiler.disable
825+
826+
Stop collecting profiling information.
827+
[clinic start generated code]*/
784828

785-
static PyObject*
786-
profiler_disable(ProfilerObject *self, PyObject* noarg)
829+
static PyObject *
830+
_lsprof_Profiler_disable_impl(ProfilerObject *self)
831+
/*[clinic end generated code: output=838cffef7f651870 input=05700b3fc68d1f50]*/
787832
{
788833
if (self->flags & POF_EXT_TIMER) {
789834
PyErr_SetString(PyExc_RuntimeError,
@@ -834,21 +879,22 @@ profiler_disable(ProfilerObject *self, PyObject* noarg)
834879
Py_RETURN_NONE;
835880
}
836881

837-
PyDoc_STRVAR(clear_doc, "\
838-
clear()\n\
839-
\n\
840-
Clear all profiling information collected so far.\n\
841-
");
882+
/*[clinic input]
883+
_lsprof.Profiler.clear
884+
885+
Clear all profiling information collected so far.
886+
[clinic start generated code]*/
842887

843-
static PyObject*
844-
profiler_clear(ProfilerObject *pObj, PyObject* noarg)
888+
static PyObject *
889+
_lsprof_Profiler_clear_impl(ProfilerObject *self)
890+
/*[clinic end generated code: output=dd1c668fb84b1335 input=fbe1f88c28be4f98]*/
845891
{
846-
if (pObj->flags & POF_EXT_TIMER) {
892+
if (self->flags & POF_EXT_TIMER) {
847893
PyErr_SetString(PyExc_RuntimeError,
848894
"cannot clear profiler in external timer");
849895
return NULL;
850896
}
851-
clearEntries(pObj);
897+
clearEntries(self);
852898
Py_RETURN_NONE;
853899
}
854900

@@ -879,74 +925,62 @@ profiler_dealloc(ProfilerObject *op)
879925
Py_DECREF(tp);
880926
}
881927

928+
/*[clinic input]
929+
_lsprof.Profiler.__init__
930+
931+
timer: object(c_default='NULL') = None
932+
timeunit: double = 0.0
933+
subcalls: bool = True
934+
builtins: bool = True
935+
936+
Builds a profiler object using the specified timer function.
937+
938+
The default timer is a fast built-in one based on real time.
939+
For custom timer functions returning integers, timeunit can
940+
be a float specifying a scale (i.e. how long each integer unit
941+
is, in seconds).
942+
[clinic start generated code]*/
943+
882944
static int
883-
profiler_init(ProfilerObject *pObj, PyObject *args, PyObject *kw)
945+
_lsprof_Profiler___init___impl(ProfilerObject *self, PyObject *timer,
946+
double timeunit, int subcalls, int builtins)
947+
/*[clinic end generated code: output=ab5498359fd34283 input=40225117dd22d4d7]*/
884948
{
885-
PyObject *timer = NULL;
886-
double timeunit = 0.0;
887-
int subcalls = 1;
888-
int builtins = 1;
889-
static char *kwlist[] = {"timer", "timeunit",
890-
"subcalls", "builtins", 0};
891-
892-
if (!PyArg_ParseTupleAndKeywords(args, kw, "|Odpp:Profiler", kwlist,
893-
&timer, &timeunit,
894-
&subcalls, &builtins))
895-
return -1;
896-
897-
if (setSubcalls(pObj, subcalls) < 0 || setBuiltins(pObj, builtins) < 0)
949+
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0)
898950
return -1;
899-
pObj->externalTimerUnit = timeunit;
900-
Py_XSETREF(pObj->externalTimer, Py_XNewRef(timer));
901-
pObj->tool_id = PY_MONITORING_PROFILER_ID;
951+
self->externalTimerUnit = timeunit;
952+
Py_XSETREF(self->externalTimer, Py_XNewRef(timer));
953+
self->tool_id = PY_MONITORING_PROFILER_ID;
902954

903955
PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
904956
if (!monitoring) {
905957
return -1;
906958
}
907-
pObj->missing = PyObject_GetAttrString(monitoring, "MISSING");
908-
if (!pObj->missing) {
909-
Py_DECREF(monitoring);
959+
self->missing = PyObject_GetAttrString(monitoring, "MISSING");
960+
Py_DECREF(monitoring);
961+
if (!self->missing) {
910962
return -1;
911963
}
912-
Py_DECREF(monitoring);
913964
return 0;
914965
}
915966

916967
static PyMethodDef profiler_methods[] = {
917968
_LSPROF_PROFILER_GETSTATS_METHODDEF
918-
{"enable", _PyCFunction_CAST(profiler_enable),
919-
METH_VARARGS | METH_KEYWORDS, enable_doc},
920-
{"disable", (PyCFunction)profiler_disable,
921-
METH_NOARGS, disable_doc},
922-
{"clear", (PyCFunction)profiler_clear,
923-
METH_NOARGS, clear_doc},
924-
{"_pystart_callback", _PyCFunction_CAST(pystart_callback),
925-
METH_FASTCALL, NULL},
926-
{"_pyreturn_callback", _PyCFunction_CAST(pyreturn_callback),
927-
METH_FASTCALL, NULL},
928-
{"_ccall_callback", _PyCFunction_CAST(ccall_callback),
929-
METH_FASTCALL, NULL},
930-
{"_creturn_callback", _PyCFunction_CAST(creturn_callback),
931-
METH_FASTCALL, NULL},
969+
_LSPROF_PROFILER_ENABLE_METHODDEF
970+
_LSPROF_PROFILER_DISABLE_METHODDEF
971+
_LSPROF_PROFILER_CLEAR_METHODDEF
972+
_LSPROF_PROFILER__PYSTART_CALLBACK_METHODDEF
973+
_LSPROF_PROFILER__PYRETURN_CALLBACK_METHODDEF
974+
_LSPROF_PROFILER__CCALL_CALLBACK_METHODDEF
975+
_LSPROF_PROFILER__CRETURN_CALLBACK_METHODDEF
932976
{NULL, NULL}
933977
};
934978

935-
PyDoc_STRVAR(profiler_doc, "\
936-
Profiler(timer=None, timeunit=None, subcalls=True, builtins=True)\n\
937-
\n\
938-
Builds a profiler object using the specified timer function.\n\
939-
The default timer is a fast built-in one based on real time.\n\
940-
For custom timer functions returning integers, timeunit can\n\
941-
be a float specifying a scale (i.e. how long each integer unit\n\
942-
is, in seconds).\n\
943-
");
944-
945979
static PyType_Slot _lsprof_profiler_type_spec_slots[] = {
946-
{Py_tp_doc, (void *)profiler_doc},
980+
{Py_tp_doc, (void *)_lsprof_Profiler___init____doc__},
947981
{Py_tp_methods, profiler_methods},
948982
{Py_tp_dealloc, profiler_dealloc},
949-
{Py_tp_init, profiler_init},
983+
{Py_tp_init, _lsprof_Profiler___init__},
950984
{Py_tp_traverse, profiler_traverse},
951985
{0, 0}
952986
};

0 commit comments

Comments
 (0)