2

I have been working on a Python C/C++ extension, I managed to get everything working so far except a method that adds two structures called "int_obj" that have an integer inside and returns a new structure called "add_int_obj".

The extension only has two methods so far, "value" that works flawlessly and "add" that is the problematic method.

I'm testing it with the following test.py script:

import intobj
val1 = intobj.IntObj(3)
val2 = intobj.IntObj(5)
print "Val1"
print val1
print "Val1 Value"
print val1.value()
print "Val2"
print val2
print "Val2 Value"
print val2.value()
val3 = intobj.add(val1, val2)
print "Val3"
print val3
print "Val3 Value"
print val3.value()

When i execute i get the following output:

$ python test.py 
Val1
<intobj.IntObj object at 0x7faf3fc8a0f0>
Val1 Value
3
Val2
<intobj.IntObj object at 0x7faf3fc8a108>
Val2 Value
5
IntObj_add start
Int Obj Value A: 3
Int Obj Value B: 5
Int Obj Value Result: 8
IntObj_add end
Val3
Segmentation fault (core dumped)

The extension method that handles adding the two structures is:

static PyObject *IntObj_add(PyObject *self,PyObject *args)
{
    std::cout<<"IntObj_add start"<<std::endl;

    PyObject *a,*b;

    IntObj *result;

    if (!PyArg_ParseTuple(args, "OO", &a, &b))
            return NULL;

    std::cout<<"Int Obj Value A: "<<int_obj_value(((IntObj*)a)->content)<<std::endl;
    std::cout<<"Int Obj Value B: "<<int_obj_value(((IntObj*)b)->content)<<std::endl;

    result = new IntObj;

    Py_INCREF(result);

    result->content = add_int_obj(((IntObj*)a)->content,((IntObj*)b)->content);

    std::cout<<"Int Obj Value Result: "<<int_obj_value(result->content)<<std::endl;

    std::cout<<"IntObj_add end"<<std::endl;

    return (PyObject*)result;
}

Since the segmentation fault occurs only outside the method when i try to print the object and not when i try to get the value my guess is that the garbage collector is deleting the PyObject that the method is trying to return.

How can i securely return the result object?

  • `new` is a pretty suspicious way to create a Python object. – user2357112 Oct 16 '17 at 23:03
  • @user2357112 would you suggest that the object should be created differently? If the structure works inside the method why would this be a problem? – Andrés Rangel Oct 16 '17 at 23:08
  • If by "works inside the method", you mean "doesn't crash *yet*", then sure, but C++ doesn't promise to crash immediately as soon as you do something wrong. There's important stuff in `result` that isn't getting initialized, like the refcount and the type pointer. – user2357112 Oct 16 '17 at 23:15
  • Interesting way to put it, I thought i was initializing it correctly by allocating memory for the object and updating the refcount with Py_INCREF(result). Is there a more standard way to initialize the object? i found Py_BuildValue looking for it but it needs a PyObject* to build from and I'm still initializing the result with new. – Andrés Rangel Oct 16 '17 at 23:25
  • 1
    The simplest way would be to call the type object, just like you would as a user of the extension module. – user2357112 Oct 16 '17 at 23:28
  • Isn't the type object declared later on, once you have the implementations of the tp_dealloc, tp_new, tp_methods, etc? how could i access it before it is defined? I'm a bit confused, can you provide an example? – Andrés Rangel Oct 16 '17 at 23:34
  • New is not the most usual way to create objects in CPython, to say the least. I think you need to post a minimal working example, so we have a something to start out. I guess your problem is creation and not the addition, what happens if you just initialize an object and return it from your c-code? – ead Oct 17 '17 at 04:59
  • I don't think I'm working with CPython, as far as i know I'm using Python 2.7 and its c_api , I added the example code and a repository to easily reproduce the problem, the repository only contains the base library, the python extension and google tests, there is no additional unnecessary code relevant to the example except for the google tests. – Andrés Rangel Oct 17 '17 at 16:58

1 Answers1

0

Thanks to @user2357112's comment and some reading refreshing my C/C++ I remembered I could define the method prior to using it, this way I could solve my problem the following way:

I defined my method in the beginning of the file like this:

static PyObject *IntObj_add(PyObject *, PyObject *);

And then after the PyTypeObject is implemented I implemented the method creating the object calling the type object instead of new as follows:

static PyObject *IntObj_add(PyObject *self, PyObject *args)
{
    PyObject *a, *b;

    IntObj *result;

    if (!PyArg_ParseTuple(args, "OO", &a, &b))
        return NULL;

    result = (IntObj *)IntObjType.tp_alloc(&IntObjType, 0);

    result->content = add_int_obj(((IntObj *)a)->content, ((IntObj *)b)->content);

    return (PyObject *)result;
}