[PyQt] sipReleaseType may destroy a derived type when it's not

Denis Rivière denis.riviere at cea.fr
Wed Jun 7 11:25:17 BST 2017


Hi,

We experience crashes using sip 4.18 and 4.19, where it works fine using 
4.17.
I have dug for a while in the code, and found out that in some cases, 
after object conversion into a class using its %ConvertToTypeCode, the 
object gets deleted after a cast to the wrong sip-derived class.

Let's say I have a C++ class AClass, with SIP bindings and conversion 
code from another type (int for instance) which instantiates a new AClass.

In another custom function (named print_float_or_aclass() in the 
example, which is quite pointless but it's just an example), the 
conversion code is invoked, the converted object is used, then 
sipReleaseType() is normally called. But this release destroys the 
object after cast to sipAClass, where it is really a AClass.

I tried to make a relatively small example to demonstrate the problem.

file: aclass.h

#ifndef ACLASS_H
#define ACLASS_H

class AClass
{
public:
   AClass( int x );
   AClass( const AClass & x );
   virtual ~AClass();

   int number() const;

private:
   int _number;
};

#endif

--

file: aclass.cpp

#include "aclass.h"


AClass::AClass( int x )
   : _number( x )
{
}

AClass::AClass( const AClass & x )
   : _number( x.number() )
{
}

AClass::~AClass()
{
}


int AClass::number() const
{
   return _number;
}

--

file: aclass.sip


%Module aclass

class AClass
{
public:
%TypeHeaderCode
#include "aclass.h"
%End

%ConvertToTypeCode

   if( sipIsErr == NULL )
   {
     if( PyInt_Check( sipPy ) )
       return 1;
     else
       return 0;
   }
   else
   {
     std::cout << "ConvertToTypeCode, instantiate new AClass\n";
     AClass *aclass = new AClass( PyInt_AsLong( sipPy ) );
     *sipCppPtr = aclass;
     return sipGetState( sipTransferObj );
   }

%End

   AClass( int x );
   AClass( const AClass & x );
   virtual ~AClass();
   int number() const;
};

%ModuleCode
#include "aclass.h"
#include <iostream>
%End

void print_float_or_aclass( SIP_PYOBJECT );
%MethodCode
   if( PyFloat_Check( a0 ) )
     std::cout << "float: " << PyFloat_AsDouble( a0 ) << std::endl;
   else
   {
     int state = 0;
     void *obj = sipForceConvertToType( a0, sipType_AClass,
           0, 0, &state, &sipIsErr );
     if( obj )
     {
       AClass *o = reinterpret_cast<AClass *>( obj );
       std::cout << "AClass: " << o->number() << std::endl;
       sipReleaseType( obj, sipType_AClass, state );
     }
   }
%End

--

this (very) minimal Makefile can be used to build it from the source 
directory:

PYTHON_INCLUDE=/usr/include/python2.7

# comment out the following 2 lines and uncomment the next to use sip 4.19
SIP_INCLUDE=/usr/include/python2.7
SIP_EXE=sip

# SIP_INCLUDE=/home/someone/sip-4.19.2/include
# SIP_EXE=/home/someone/sip-4.19.2/bin/sip

all:    aclass.so

sipaclasspart0.cpp: aclass.sip aclass.cpp aclass.h
     $(SIP_EXE) -j1 -c . aclass.sip

aclass.so:    sipaclasspart0.cpp
     g++ -fPIC -shared -o aclass.so -I$(SIP_INCLUDE) -I$(PYTHON_INCLUDE) 
sipaclasspart0.cpp aclass.cpp

--

When using for instance aclass.print_float_or_aclass(12), 12 (int) gets 
converted to a AClass object, which is released after use.
But the release code does not do the same when using sip 4.17 or 
4.18/4.19. We can print things by slighty modifying the code of the 
release_AClass() function in the generated file sipclasspart0.cpp:

with sip 4.19.2:

/* Call the instance's destructor. */
extern "C" {static void release_AClass(void *, int);}
static void release_AClass(void *sipCppV, int sipIsDerived)
{
     std::cout << "release_AClass, sipIsDerived: " << sipIsDerived << 
std::endl;
     if (sipIsDerived)
     {
         std::cout << "delete derived\n";
         delete reinterpret_cast<sipAClass *>(sipCppV);
     }
     else
     {
         std::cout << "delete AClass\n";
         delete reinterpret_cast< ::AClass *>(sipCppV);
     }
}


with sip 4.17:

/* Call the instance's destructor. */
extern "C" {static void release_AClass(void *, int);}
static void release_AClass(void *sipCppV,int sipState)
{
     std::cout << "release_AClass, sipState: " << sipState << std::endl;
     if (sipState & SIP_DERIVED_CLASS)
     {
         std::cout << "delete derived\n";
         delete reinterpret_cast<sipAClass *>(sipCppV);
     }
     else
     {
         std::cout << "delete AClass\n";
         delete reinterpret_cast<AClass *>(sipCppV);
     }
}

Then after rebuilding the module, we use the function:

 >>> import aclass
 >>> aclass.print_float_or_aclass(12)

with sip 4.17, it prints:

ConvertToTypeCode, instantiate new AClass
AClass: 12
release_AClass, sipState: 1
delete AClass

(so it is not the derived class case)

with sip 4.19 (I have changed PYTHONPATH to point to the sip 4.19 module 
before running), it prints:

ConvertToTypeCode, instantiate new AClass
AClass: 12
release_AClass, sipIsDerived: 1
delete derived

(note that it does not crash every time, but seems wrong anyway)

Here the release code is called with sipIsDerived argument set to 1. 
However 1 is actually directly the "state" value set in 
%ConvertToTypeCode, which does not mean SIP_DERIVED_CLASS (which value 
is 2), but SIP_TEMPORARY.

In the source code of siplib (siplib.c.in), the code of 
sip_api_release_type() calls the release function of the type object 
with complete "state" value, not a boolean meaning if it is derived.

So I think there is a bug there.
Am I right ?

Regards,
Denis



More information about the PyQt mailing list