[Cython] segfault due to using DECREF instead of XDECREF

Victor Makarov vitja.makarov at gmail.com
Thu Jan 23 21:02:03 CET 2014


2014/1/23 Syam Gadde <syam.gadde at duke.edu>:
> On 01/23/2014 01:04 PM, Stefan Behnel wrote:
>>
>> BTW, thanks for cutting down the code to a short and easy to analyse test
>> case, but if the original code really has a control flow like that, there
>> might be some space left for improved clarity.
>
>
> Absolutely.  In the wild, it manifested itself in PyFFTW.  It turns out I
> will likely not be using it, but thought it was an interesting edge case,
> and was what looked like valid Python, though it plays hard and fast with
> Python's runtime binding...
>
> -syam
>
>
> On 01/23/2014 01:04 PM, Stefan Behnel wrote:
>>
>> Syam Gadde, 21.01.2014 23:00:
>>>
>>> It seems that cython's static code analysis is failing on the following
>>> code.  It thinks that a particular variable (myobject2) must not be
>>> NULL, but in fact it is, so the __Pyx_DECREF_SET segfaults.  This came
>>> from third-party code, so I could fix it with an explicit initialization
>>> to None, but I'd love to have Cython deal with it directly.  Thanks for
>>> your help!
>>>
>>> # When compiled with Cython 0.19.2 or 0.20dev, the following code
>>> # segfaults on import
>>>
>>> import random
>>>
>>> def myfunc():
>>>       ## uncommenting the following fixes things
>>>       #myobject2 = None
>>>
>>>       myfalse = random.random() > 2
>>>
>>>       if myfalse:
>>>           myobject = None
>>>           myobject2 = None
>>>
>>>       if not myfalse:
>>>           # here Cython uses __Pyx_XDECREF_SET
>>>           myobject = None
>>>
>>>       print "Made it past myobject assignment"
>>>
>>>       if not myfalse or myobject2 is None:
>>>           # here Cython uses Pyx_DECREF_SET because it has determined
>>>           # that __pyx_v_myobject2 can't be null, but it really, really
>>> can!
>>>           # (if no one assigned myobject2 yet)
>>>           myobject2 = None
>>>
>>>       print "Made it past myobject2 assignment"
>>>
>>> myfunc()
>>
>> Thanks for the report. The generated C code currently looks like this:
>>
>> ===================
>> /*
>>   *      if not myfalse or myobject2 is None:             # <<<<<<<<<<<<<<
>>   */
>>    __pyx_t_4 = __Pyx_PyObject_IsTrue(__pyx_v_myfalse); if /*error*/
>>    __pyx_t_3 = ((!__pyx_t_4) != 0);
>>    if (!__pyx_t_3) {
>>      if (unlikely(!__pyx_v_myobject2)) {
>>                       __Pyx_RaiseUnboundLocalError("myobject2"); {...} }
>>      __pyx_t_4 = (__pyx_v_myobject2 == Py_None);
>>      __pyx_t_5 = (__pyx_t_4 != 0);
>>    } else {
>>      __pyx_t_5 = __pyx_t_3;
>>    }
>>    if (__pyx_t_5) {
>>
>> /*
>>   *          myobject2 = None             # <<<<<<<<<<<<<<
>>   */
>>      __Pyx_INCREF(Py_None);
>>      __Pyx_DECREF_SET(__pyx_v_myobject2, Py_None);
>> ===================
>>
>> So, it's smart enough to figure out that it has to raise an
>> UnboundLocalError on the "if" test if the variable hasn't been set yet,
>> but
>> then fails to see that in the case that the condition short-circuits, this
>> test hasn't been executed yet.
>>
>> BTW, thanks for cutting down the code to a short and easy to analyse test
>> case, but if the original code really has a control flow like that, there
>> might be some space left for improved clarity.
>>
>> Stefan
>>


Hi!

It's a bug! Being referenced in expression varaiable is marked as not
null. But it doesn't take into account that some subexpressions wasn't
evaluated. Here is another example:

def crashme(a=True):
    if not a:
        b = []
    (a or b) and (b or a)
crashme()

It seems to me that reference shouldn't affect variable's flags since
we don't have a simple way to track evaluation order.

I'd like to fix this if you don't mind.


-- 
vitja.


More information about the cython-devel mailing list