None value PyObject to NULL in PyArg_ParseTupleAndKeywords()

4.2k views Asked by At

I am passing an extension type object to a Python function, which needs to pass a variable in this type to a C function. My extension type looks like this:

typedef struct {
    PyObject_HEAD
    rec_rset_t rst;  //need to pass this to C function
} RSet;

The rec_rset_t is a pointer to a struct, like this:

typedef struct rec_rset_s *rec_rset_t;

where rec_rset_s is defined thus:

struct rec_rset_s
{
  size_t size; 
  int a,b;
}

So I have a Python extension function, that receives an RSet object as an argument.

static PyObject*
Recon_query(Recon *self, PyObject *args, PyObject *kwds)
{
    const char  *type;
    RSet        *tmp = PyObject_NEW(RSet, &RSetType);
    rec_rset_t  res;
    static char *kwlist[] = {"type", "rset", NULL};
    if (! PyArg_ParseTupleAndKeywords(args, kwds, "zO", kwlist,
                                      &type, &rset))
      { 
        return NULL;
      }
    res = cquery(self->rcn, type, rset->rst);
    tmp->rst = res;
    return Py_BuildValue("O",tmp);
}

The problem is that I want to be able to pass None for the RSet object, and have it translate to NULL in C as well as have the variable rst be NULL. If I pass None, I get a segmentation fault, because the "O" option of PyArg_ParseTupleAndKeywords does not handle None value of PyObject (this is unlike the "s" option, where we can use "z" if we are passing a NULL string). I tried manually checking for Py_None object, but it didn't work. So at present I'm doing something not very elegant, like this:

if(rset->rst == 0x89e8a0)
    rset->rst = NULL;

because that was the value of rset->rst when I passed None, and then passing this rset->rst to the cquery function. How does one pass None values to PyArg_ParseTuple when receiving an extension type object? Is there a general way in which this is done?

Edit:

I had wrongly checked the value of rset->rst for Py_None. Checking

if((PyObject *)rset == Py_None)

evaluates to true, so yes, None is handled. But the value rset->rst (which I pass to cquery) is not NULL, which is what I want. Is manually setting rset->rst = NULL the only way to do this?

1

There are 1 answers

0
arjache On

The problem is that if rset is set to Py_None (a PyObject*), only the PyObject_HEAD-defined fields are guaranteed to be valid; essentially you've gotten back a pointer to something that isn't an RSet.

Instead of setting rset->rst to NULL (which, if rset == Py_None, could be modifying memory that it shouldn't be), you should modify your code so that it initially refers to a PyObject* and then only casts to an RSet* once it is certain it is not Py_None:

PyObject *rsetobj = NULL;
rec_rset_t rst;
if (! PyArg_ParseTupleAndKeywords(args, kwds, "zO", kwlist,
                                      &type, &rsetobj))
      { 
        return NULL;
      }
if (rsetobj == Py_None)
    rst = NULL;
else
    rst = ((RSet*)rsetobj)->rst;
res = cquery(self->rcn, type, rst);

You might also want to explicitly check the object type before casting to RSet. (Or use O!, which does type validation for you, but I seem to recall that doesn't allow for None, which you want in this case.)