Search code examples
pythonpython-3.xcpythonpython-c-apipython-extensions

PyObject_Call segfaults when invoked with bound method


PyObject_Call segfaults when it is called with an instance of a bound method, but works fine when invoked with regular (unbound) procedures or with an instance of a class that implements __call__, or with subclasses of type.

Any reason it should work like this, or is this a bug? The behavior is consistent between v 3.5 and 3.6. Didn't try earlier versions.

PS. The stacktrace produced in this scenario doesn't even contain my code. However, if it matters, the crash happens inside

method_dealloc () at Objects/classobject.c:194

which looks like this: https://github.com/python/cpython/blob/master/Objects/classobject.c#L194

To prevent the immediate question: yes, I call Py_INCREF(callable) before calling this procedure.


More info

When I try to look at what is being sent into this call, I see something like this:

found method: <bound method DefParser.parse of <bound method DefParser.update_definition of <NULL>>>

The DefParser.parse and DefParser.update_definition are not exactly random, but also not exactly relevant: they are methods that have been called recently. I.e. I suspect that PyObject_Call itself isn't guilty, it's just the way method objects are represented... for some reason I seem to lose the reference, and instead hold on to garbage...


Solution

  • A lot of investigation found the actual error. It wasn't related to PyObject_Call in the end, but it may help others who might run into this situation.

    PyObject_Call is one of the Python C API which allocates memory. Python's memory allocator will opportunistically call GC. I'm not sure on specifics of when it decides to do it, but eventually it will happen. GC will then try to free memory allocated by Python objects.

    What happened in my case: there was a string allocated using regular malloc, where I miscalculated the position of the terminating null-byte (in some cases it would appear one position after the memory I requested to be allocated). This memory was then used to create a Python bytes object. Later one, when GC would de-allocate this object, it would seagfault.