Skip to content

Commit c9f1312

Browse files
authored
Merge pull request #394 from joerick/memory-leaks
Fix some memory leaks discovered in a debugging session
2 parents 829b05b + 3a8b819 commit c9f1312

File tree

1 file changed

+37
-8
lines changed

1 file changed

+37
-8
lines changed

pyinstrument/low_level/stat_profile.c

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ static int ProfilerState_UpdateContextVar(ProfilerState *self) {
6666
return 0;
6767
}
6868

69-
if (old == new) return 1;
69+
if (old == new) {
70+
// The object is the same, so we don't need the new reference.
71+
Py_DECREF(new);
72+
return 1;
73+
}
7074

7175
self->last_context_var_value = new;
7276

@@ -251,6 +255,9 @@ call_target(ProfilerState *pState, PyFrameObject *frame, int what, PyObject *arg
251255
return result;
252256
}
253257

258+
/**
259+
* Returns a new reference to a PyCodeObject for the given frame.
260+
*/
254261
static PyCodeObject *
255262
code_from_frame(PyFrameObject* frame)
256263
{
@@ -263,6 +270,10 @@ code_from_frame(PyFrameObject* frame)
263270
#endif
264271
}
265272

273+
/**
274+
* Returns a new reference to a PyTupleObject containing the names of the
275+
* local variables.
276+
*/
266277
static PyObject *
267278
local_names_from_code(PyCodeObject *code)
268279
{
@@ -276,6 +287,10 @@ local_names_from_code(PyCodeObject *code)
276287
}
277288

278289
#if PY_VERSION_HEX >= 0x030b0000 // Python 3.11.0
290+
/**
291+
* Returns a C-string containing the name of the class in the frame. The
292+
* memory belongs to the type object, so it should not be freed.
293+
*/
279294
static const char *
280295
_get_class_name_of_frame(PyFrameObject *frame, PyCodeObject *code) {
281296
PyObject *localsNames = PyCode_GetVarnames(code);
@@ -287,10 +302,10 @@ _get_class_name_of_frame(PyFrameObject *frame, PyCodeObject *code) {
287302
PyObject *firstArgName = PyTuple_GET_ITEM(localsNames, 0);
288303

289304
if (firstArgName == NULL) {
305+
Py_DECREF(localsNames);
290306
return NULL;
291307
}
292308

293-
294309
int has_self = PyUnicode_Compare(firstArgName, SELF_STRING) == 0;
295310
int has_cls = PyUnicode_Compare(firstArgName, CLS_STRING) == 0;
296311

@@ -323,6 +338,7 @@ _get_class_name_of_frame(PyFrameObject *frame, PyCodeObject *code) {
323338
}
324339

325340
result = _PyType_Name(self->ob_type);
341+
Py_DECREF(self);
326342
}
327343
else if (has_cls && PyMapping_HasKey(locals, CLS_STRING)) {
328344
PyObject *cls = PyObject_GetItem(locals, CLS_STRING);
@@ -337,6 +353,7 @@ _get_class_name_of_frame(PyFrameObject *frame, PyCodeObject *code) {
337353
PyTypeObject *type = (PyTypeObject *)cls;
338354
result = _PyType_Name(type);
339355
}
356+
Py_DECREF(cls);
340357
}
341358

342359
Py_DECREF(locals);
@@ -456,6 +473,7 @@ _get_tracebackhide(PyFrameObject *frame, PyCodeObject *code) {
456473

457474
if (!PySequence_Check(locals_names)) {
458475
// locals_names must be a sequence
476+
Py_DECREF(locals_names);
459477
return 0;
460478
}
461479

@@ -471,6 +489,9 @@ _get_tracebackhide(PyFrameObject *frame, PyCodeObject *code) {
471489
}
472490
}
473491

492+
/**
493+
* Returns a new reference to pyinstrument's frame info string for the given frame.
494+
*/
474495
static PyObject *
475496
_get_frame_info(PyFrameObject *frame) {
476497
PyCodeObject *code = code_from_frame(frame);
@@ -703,14 +724,18 @@ setstatprofile(PyObject *m, PyObject *args, PyObject *kwds)
703724
}
704725

705726
pState = ProfilerState_New();
727+
if (pState == NULL) { // Check if allocation failed
728+
return NULL;
729+
}
730+
706731
ProfilerState_SetTarget(pState, target);
707732

708733
// default interval is 1 ms
709734
pState->interval = (interval > 0) ? interval : 0.001;
710735

711736
int timer_type_int = _parse_timer_type(timer_type, TIMER_TYPE_WALLTIME);
712737
if (timer_type_int == -1) {
713-
return NULL;
738+
goto error;
714739
}
715740

716741
if (timer_func == Py_None) {
@@ -719,12 +744,12 @@ setstatprofile(PyObject *m, PyObject *args, PyObject *kwds)
719744

720745
if (timer_type_int == TIMER_TYPE_TIMER_FUNC && timer_func == NULL) {
721746
PyErr_SetString(PyExc_TypeError, "timer_func must be set if timer_type is 'timer_func'");
722-
return NULL;
747+
goto error;
723748
}
724749

725750
if (timer_func && timer_type_int != TIMER_TYPE_TIMER_FUNC) {
726751
PyErr_SetString(PyExc_TypeError, "timer_type must be 'timer_func' if timer_func is set");
727-
return NULL;
752+
goto error;
728753
}
729754

730755
if (timer_func) {
@@ -734,7 +759,7 @@ setstatprofile(PyObject *m, PyObject *args, PyObject *kwds)
734759
pState->timer_thread_subscription_id = pyi_timing_thread_subscribe(pState->interval);
735760
if (pState->timer_thread_subscription_id < 0) {
736761
PyErr_Format(PyExc_RuntimeError, "failed to subscribe to timing thread: error %d", pState->timer_thread_subscription_id);
737-
return NULL;
762+
goto error;
738763
}
739764
} else if (timer_type_int == TIMER_TYPE_WALLTIME_COARSE) {
740765
pState->floatclock_type = PYI_FLOATCLOCK_MONOTONIC_COARSE;
@@ -750,17 +775,21 @@ setstatprofile(PyObject *m, PyObject *args, PyObject *kwds)
750775
pState->context_var = context_var;
751776

752777
if (!ProfilerState_UpdateContextVar(pState)) {
753-
return NULL;
778+
goto error;
754779
}
755780
}
756781

757782
PyEval_SetProfile(profile, (PyObject *)pState);
758-
Py_DECREF(pState);
783+
Py_DECREF(pState); // We've given a reference to SetProfile, so we release ours.
759784
} else {
760785
PyEval_SetProfile(NULL, NULL);
761786
}
762787

763788
Py_RETURN_NONE;
789+
790+
error:
791+
Py_XDECREF(pState);
792+
return NULL;
764793
}
765794

766795

0 commit comments

Comments
 (0)