tuple creation in C extensions

Bernhard Herzog herzog at online.de
Tue Jun 13 15:53:25 EDT 2000


Alex <cut_me_out at hotmail.com> writes:

> > Do keep digging. Ref-counting is important.
> 
> Ref-counting errors can be such a pain in the neck to track down that I
> have taken to placing assertions at the end of routines to make sure
> the ref-counts are what they ought to be.

That's usually very hard to get right. It's usually very hard to guess
what the reference count of a given object should be.

> This is the sort of thing I've been doing:
> 
> int dict_add_pattern (PyObject *dict, char *string, int length, long pos[DEPTH]) {
>   PyObject *pattern, *coords, *value, *tuple;
>   int return_code, pattern_existed;
> 
>   assert (length <= DEPTH);
> 
>   coords = dict_set_coords (length - 1, pos);
>   if (!coords) {goto fail;}
>   
>   pattern = Py_BuildValue ("s#N", string, length, coords);
>   if (!pattern) {Py_DECREF (coords); goto fail;}
 
>   value = PyDict_GetItem(dict, pattern);
>   if (!PyDict_GetItem(dict, pattern)) {
>     pattern_existed = 0;
>     value = PyInt_FromLong( 1 );
>     return_code = PyDict_SetItem(dict, pattern, value);

You have to DECREF value here.

>   } else {
>     pattern_existed = 1;
>     assert (PyInt_Check( value ));
>     value = PyNumber_Add( value, PyInt_FromLong( 1 ));

Here's a memory leak for the old value and the 'new' intobject returned
by PyInt_FromLong. 

>     if (value == NULL) {goto fail;}

If value is indeed NULL, you leak the reference to pattern

>     return_code = PyDict_SetItem(dict, pattern, value);

You have to DECREF value here.

>   }
>   /* If a tuple equal to pattern was already in dict, then pattern will not be
>      referred to by dict, and should have a refcnt of 0.  If there was no such
>      tuple, then PyDict_SetItem increased the refcnt of dict by one.  In
>      either case, the refcnt has to be decremented, now.
>      */
>   Py_DECREF (pattern);

The DECREF of pattern may delete pattern so pattern should not be
dereferenced after this point. Since pattern has the only reference to
coords, coords may also have been deleted.

>       
>   if (return_code == -1) {
>     goto fail;
>   } else {
>     if (DEBUG_REFCOUNTS) {
>       printf ("coords: %i\npattern: %i\nvalue: %i\n",
> 	      coords->ob_refcnt, pattern->ob_refcnt, value->ob_refcnt);
>     }
>     if (DEBUG_LIST_PATTERNS) {
>       PyObject_Print (dict, stdout, 0); printf ("\n");
>     }
>     goto pass;
>   }
> 
> fail:
>   return 0;
> pass:
>   assert (pattern->ob_refcnt == (!pattern_existed));
>   assert ((coords->ob_refcnt == (!pattern_existed)) | (length == 1));

You dereference potentially free()'ed pointers here and in the debug
messages above. To make things worse, if DEBUG_LIST_PATTERNS is true,
PyObject_Print might execute pretty arbitrary Python code which might
result in the memory being reused again.

>   return 1;
> }

-- 
Bernhard Herzog   | Sketch, a drawing program for Unix
herzog at online.de  | http://sketch.sourceforge.net/



More information about the Python-list mailing list