[PyQt] sip 4.19 and virtual inheritance

Denis Rivière denis.riviere at cea.fr
Fri Feb 8 02:03:48 GMT 2019


This test example demonstrates a problem in handling virtual / diamond
inheritance in conjunction with multiple SIP modules uing sip 4.19.x.

We are working with the following class schema:


AClass       BClass
   \        /     \
    \   (virtual)  (virtual)
     \    /         \
      CClass        DClass
          \        /
           \      /
            EClass


The archive attached contains the code for these classes, and sip bindings
in 2 different situations: a single sip module, or 2 modules (to "simulate"
bindings to 2 different dependent libraries). We experience difficulties in
the second situation using sip 4.19.x when it works mainly well using sip
4.16.
There is an old-school Makefile to have the simplest possible build.


A. Single module:
In this case we bind all 4 classes in a single sip module, testmodule
(testmodule.sip).
In python we can run:

In [1]: import testmodule

In [2]: a = testmodule.get_a()
B: 0x7f282a26be78
A: 0x7f282a26be68
C: 0x7f282a26be68
D: 0x7f282a26be88
E: 0x7f282a26be60
ConvertToSubClassCode AClass: 0x7f282a26be68
I'm a CClass: 0x7f282a26be68
I'm a EClass: 0x7f282a26be60

In [3]: c = testmodule.get_c()
ConvertToSubClassCode AClass: 0x7f282a26be68
I'm a CClass: 0x7f282a26be68
I'm a EClass: 0x7f282a26be60

In [4]: a
Out[4]: <testmodule.EClass at 0x7f282b344478>

In [5]: c
Out[5]: <testmodule.EClass at 0x7f282b344478>

In [6]: d = testmodule.get_d()

In [7]: d
Out[7]: <testmodule.EClass at 0x7f282b344478>

In [8]: e = testmodule.get_e()

In [9]: e
Out[9]: <testmodule.EClass at 0x7f282b344478>

In [10]: a.print_this_a()
A: 0x7f282a26be68

In [11]: a.print_this_b()
B: 0x7f282a26be60

In [12]: a.print_this_c()
C: 0x7f282a26be68

In [13]: a.print_this_d()
D: 0x7f282a26be88

In [14]: a.print_this_e()
E: 0x7f282a26be60

Here all seems OK, all get_*() functioons return a EClass instance, and the
pointers to different "parts" of the class seem consistent.


B. Split in 2 modules: classes AClass, BClass, CClass are in module
testmodule1, and DClass, EClass are in module testmodule2 (testmodule1.sip
and testmodule2.sip).
Here we have to make %ConvertToSubClassCode a bit differently due to the
split in 2 modules.


B1. When built using sip 4.19 (tried with 4.19.3 from ubuntu 18.04 and
4.19.13 built from sources), we are running:

In [1]: import testmodule2

In [2]: a = testmodule2.get_a()
B: 0x7fd9ba00a898
A: 0x7fd9ba00a888
C: 0x7fd9ba00a888
D: 0x7fd9ba00a8a8
E: 0x7fd9ba00a880
ConvertToSubClassCode AClass: 0x7fd9ba00a888
I'm a CClass: 0x7fd9ba00a888

In [3]: a
Out[3]: <testmodule1.CClass at 0x7fd9bb0e2478>

In [4]: a.print_this_a()
A: 0x7fd9ba00a888

In [5]: a.print_this_b()
B: 0x7fd9ba00a898

In [6]: a.print_this_c()
C: 0x7fd9ba00a888

In [7]: c = testmodule2.get_c()

In [8]: c
Out[8]: <testmodule1.CClass at 0x7fd9bb0e2478>

In [9]: d = testmodule2.get_d()
ConvertToSubClassCode DClass: 0x7fd9ba00a8a8
Erreur de segmentation (core dumped)

Here we see that:
* AClass and CClass are not cast to EClass, the %ConvertToSubClassCode for
testmodule2 classes are not called
* get_d() segfaults. In the sip generated code siptestmodule2part0.cpp, in
the function sipSubClass_DClass(), we see that sipCpp is a BClass:

     ::BClass *sipCpp = reinterpret_cast< ::BClass *>(*sipCppRet);

but this is a reinterpret_cast to BClass* from a pointer actually being the
address of the DClass part, which is not the location of the virtual BClass
part, which explains the crash.


B2. If I remove all the ConvertToSubClassCode for all classes, then all
pointers seem OK, but we have no conversion to subtypes.


B3. If I set the ConvertToSubClassCode for class AClass only, then
testmodule2.get_e() segfaults:

n [1]: import testmodule2

In [2]: a = testmodule2.get_a()
B: 0x7f928d44b838
A: 0x7f928d44b828
C: 0x7f928d44b828
D: 0x7f928d44b848
E: 0x7f928d44b820
ConvertToSubClassCode AClass: 0x7f928d44b828
I'm a CClass: 0x7f928d44b828

In [3]: a
Out[3]: <testmodule1.CClass at 0x7f928dd24770>

In [4]: e = testmodule2.get_e()
ConvertToSubClassCode AClass: 0x7f928d44b820
Erreur de segmentation (core dumped)

Here the pointer value corresponding to EClass part is reinterpreted as a
AClass.


B4. Using sip 4.16 (as in Ubuntu 16.04) I don't have any of these crashes,
all pointers are consistent. When all ConvertToSubClassCode are here, we
have:

In [1]: import testmodule2

In [2]: a = testmodule2.get_a()
B: 0x7f6cc373e878
A: 0x7f6cc373e868
C: 0x7f6cc373e868
D: 0x7f6cc373e888
E: 0x7f6cc373e860
ConvertToSubClassCode AClass: 0x7f6cc373e868
I'm a CClass: 0x7f6cc373e868

In [3]: c = testmodule2.get_c()
ConvertToSubClassCode DClass: 0x7f6cc373e878

In [4]: d = testmodule2.get_d()
ConvertToSubClassCode DClass: 0x7f6cc373e878

In [5]: e = testmodule2.get_e()
ConvertToSubClassCode DClass: 0x7f6cc373e878
ConvertToSubClassCode EClass: 0x7f6cc373e878
I'm a EClass: 0x7f6cc373e860
ConvertToSubClassCode AClass: 0x7f6cc373e868
I'm a CClass: 0x7f6cc373e868
ConvertToSubClassCode BClass: 0x7f6cc373e878
I'm a CClass: 0x7f6cc373e868

In [6]: a
Out[6]: <testmodule1.CClass at 0x7f6cc8870510>

In [7]: c
Out[7]: <testmodule2.EClass at 0x7f6cc88708a0>

In [8]: d
Out[8]: <testmodule2.EClass at 0x7f6cc88708a0>

In [9]: e
Out[9]: <testmodule2.EClass at 0x7f6cc88708a0>

so here get_a() (returning AClass* in C++) is cast to CClass, and all other
get_* functions are cast to EClass. Otherwise all the conversion functions
get a pointer to BClass (except the one for AClass), which is consistent
with the type used in the function.
So here I just wonder why get_a() does not call conversion to more subtypes.


So we definitely see different behaviours using sip 4.16 and 4.19 when
subclass conversion code is used, and I wonder if I am doing something
wrong in these conversion codes, or if there is a problem in sip 4.19. The
B3 test above, at least, let me think that there is a problem in sip, but I
can't be sure of it.

In a different aspect, I also wonder: what is the expected type of sipCpp
in a ConvertToSubClassCode section ? The doc is not completely explicit
about it, since it both seems to suggest that it has the type of the class
where the %ConvertToSubClassCode section is, and also says that this
conversion code can be in any of the classes in the inheritance three,
which is in contradiction with this suggestion. Here we get a BClass in
some functions, and AClass in another. In a set of bindings of several of
our libraries, I think I have seen cases when the type of sipCpp is not the
same using sip 4.16 and 4.19 but I have not been able to reproduce it in a
simple example.

Thanks for any help, and sorry for the long message.

Denis


As I'm not sure the mailing list pyqt at riverbankcomputing.com accepts
attachments, here is a dump of the files


/* --- testmodule1.h --- */

#ifndef TESTMODULE1_H
#define TESTMODULE1_H

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

  int a() const { return _number; }
  void set_a( int x ) { _number = x; }

  void print_this_a();

private:
  int _number;
};


class BClass
{
public:
  BClass();
  virtual ~BClass();

  void set_b( int x ) { _bnumber = x; }
  int b() const { return _bnumber; }

  void print_this_b();

private:
  int _bnumber;
};


class CClass: public AClass, public virtual BClass
{
public:
  CClass();
  virtual ~CClass();

  void print_this_c();
};

#endif


/* --- testmodule2.h --- */

#ifndef TESTMODULE2_H
#define TESTMODULE2_H

#include "testmodule1.h"

class DClass : public virtual BClass
{
public:
  DClass();
  virtual ~DClass();

  void set_d( int x ) { _dnumber = x; }
  int d() const { return _dnumber; }

  void print_this_d();

private:
  int _dnumber;
};


class EClass: public virtual CClass, public virtual DClass
{
public:
  EClass();
  virtual ~EClass();

  void print_this_e();
};


AClass* get_a();
// BClass* get_b();
CClass* get_c();
DClass* get_d();
EClass* get_e();

#endif


/* --- testmodule1.cpp --- */


#include "testmodule1.h"
#include <iostream>


AClass::AClass( int x )
  : _number( x )
{
    std::cout << "A: " << this << std::endl;
}

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

AClass::~AClass()
{
}


void AClass::print_this_a()
{
  std::cout << "A: " << this << std::endl;
}


BClass::BClass()
    : _bnumber(3)
{
  std::cout << "B: " << this << std::endl;
}


BClass::~BClass()
{
}


void BClass::print_this_b()
{
  std::cout << "B: " << this << std::endl;
}


CClass::CClass() : AClass(), BClass()
{
  std::cout << "C: " << this << std::endl;
}


CClass::~CClass()
{
}


void CClass::print_this_c()
{
  std::cout << "C: " << this << std::endl;
}


/* --- testmodule2.cpp --- */


#include "testmodule2.h"
#include <iostream>


DClass::DClass()
  : BClass(), _dnumber( 7 )
{
  std::cout << "D: " << this << std::endl;
}


DClass::~DClass()
{
}


void DClass::print_this_d()
{
  std::cout << "D: " << this << std::endl;
}


EClass::EClass()
  : CClass(), DClass()
{
  std::cout << "E: " << this << std::endl;
}


EClass::~EClass()
{
}


void EClass::print_this_e()
{
  std::cout << "E: " << this << std::endl;
}


EClass & e_instance()
{
  static EClass e;
  return e;
}


AClass *get_a()
{
  return &e_instance();
}


// BClass *get_b()
// {
//   return static_cast<BClass *>( &f_instance() );
// }


CClass *get_c()
{
  return &e_instance();
}


DClass *get_d()
{
  return &e_instance();
}


EClass *get_e()
{
  return &e_instance();
}


/* --- testmodule1.sip --- */


%Module testmodule1

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

%ConvertToSubClassCode
  std::cout << "ConvertToSubClassCode AClass: " << sipCpp << std::endl;
  // sipType = sipType_AClass;
  if( dynamic_cast<CClass *>( sipCpp ) )
  {
    sipType = sipType_CClass;
    *sipCppRet = dynamic_cast<CClass *>( sipCpp );
    std::cout << "I'm a CClass: " << *sipCppRet << std::endl;
  }
%End

  AClass( int x );
  AClass( const AClass & x );
  virtual ~AClass();
  int a() const;
  void set_a( int x );
  void print_this_a();
};


class BClass
{
public:
%TypeHeaderCode
#include "testmodule1.h"
%End

%ConvertToSubClassCode
  std::cout << "ConvertToSubClassCode BClass: " << sipCpp << std::endl;
  // sipType = sipType_BClass;
  if( dynamic_cast<CClass *>( sipCpp ) )
  {
    sipType = sipType_CClass;
    *sipCppRet = dynamic_cast<CClass *>( sipCpp );
    std::cout << "I'm a CClass: " << *sipCppRet << std::endl;
  }
%End

  BClass();
  virtual ~BClass();
  int b() const;
  void set_b( int x );
  void print_this_b();

};

class CClass: AClass, BClass
{
public:
%TypeHeaderCode
#include "testmodule1.h"
%End

  CClass();
  virtual ~CClass();
  void print_this_c();
};


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


/* --- testmodule2.sip --- */


%Import testmodule1.sip

%Module testmodule2

class DClass : BClass
{
public:
%TypeHeaderCode
#include "testmodule2.h"
%End

%ConvertToSubClassCode
  std::cout << "ConvertToSubClassCode DClass: " << sipCpp << std::endl;
  // sipType = sipType_DClass;
  if( dynamic_cast<DClass *>( sipCpp ) )
  {
    sipType = sipType_DClass;
    *sipCppRet = dynamic_cast<DClass *>( sipCpp );
  }
  if( dynamic_cast<EClass *>( sipCpp ) )
  {
    sipType = sipType_EClass;
    *sipCppRet = dynamic_cast<EClass *>( sipCpp );
  }
%End

  DClass();
  virtual ~DClass();
  int d() const;
  void set_d( int x );
  void print_this_d();
};


class EClass : CClass, DClass
{
public:
%TypeHeaderCode
#include "testmodule2.h"
%End

%ConvertToSubClassCode
  std::cout << "ConvertToSubClassCode EClass: " << sipCpp << std::endl;
  // sipType = sipType_EClass;
  if( dynamic_cast<EClass *>( sipCpp ) )
  {
    sipType = sipType_EClass;
    *sipCppRet = dynamic_cast<EClass *>( sipCpp );
    std::cout << "I'm a EClass: " << *sipCppRet << std::endl;
  }
  else if( dynamic_cast<CClass *>( sipCpp ) )
  {
    sipType = sipType_CClass;
    *sipCppRet = dynamic_cast<CClass *>( sipCpp );
    std::cout << "I'm a CClass: " << *sipCppRet << std::endl;
  }
  else if( dynamic_cast<DClass *>( sipCpp ) )
  {
    sipType = sipType_DClass;
    *sipCppRet = dynamic_cast<DClass *>( sipCpp );
    std::cout << "I'm a DClass: " << *sipCppRet << std::endl;
  }
%End

  EClass();
  virtual ~EClass();
  void print_this_e();

};


AClass* get_a();
CClass* get_c();
DClass* get_d();
EClass* get_e();

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


/* --- testmodule.sip --- */


%Module testmodule

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

%ConvertToSubClassCode
  std::cout << "ConvertToSubClassCode AClass: " << sipCpp << std::endl;
  // sipType = sipType_AClass;
  if( dynamic_cast<CClass *>( sipCpp ) )
  {
    sipType = sipType_CClass;
    *sipCppRet = dynamic_cast<CClass *>( sipCpp );
    std::cout << "I'm a CClass: " << *sipCppRet << std::endl;
  }
  if( dynamic_cast<EClass *>( sipCpp ) )
  {
    sipType = sipType_EClass;
    *sipCppRet = dynamic_cast<EClass *>( sipCpp );
    std::cout << "I'm a EClass: " << *sipCppRet << std::endl;
  }
%End

  AClass( int x );
  AClass( const AClass & x );
  virtual ~AClass();
  int a() const;
  void set_a( int x );
  void print_this_a();
};


class BClass
{
public:
%TypeHeaderCode
#include "testmodule1.h"
%End

%ConvertToSubClassCode
  std::cout << "ConvertToSubClassCode BClass: " << sipCpp << std::endl;
  // sipType = sipType_BClass;
  if( dynamic_cast<CClass *>( sipCpp ) )
  {
    sipType = sipType_CClass;
    *sipCppRet = dynamic_cast<CClass *>( sipCpp );
    std::cout << "I'm a CClass: " << *sipCppRet << std::endl;
  }
  if( dynamic_cast<EClass *>( sipCpp ) )
  {
    sipType = sipType_EClass;
    *sipCppRet = dynamic_cast<EClass *>( sipCpp );
    std::cout << "I'm a EClass: " << *sipCppRet << std::endl;
  }
%End


  BClass();
  virtual ~BClass();
  int b() const;
  void set_b( int x );
  void print_this_b();

};


class CClass: AClass, BClass
{
public:
%TypeHeaderCode
#include "testmodule1.h"
%End

  CClass();
  virtual ~CClass();
  void print_this_c();
};


class DClass : BClass
{
public:
%TypeHeaderCode
#include "testmodule2.h"
%End

%ConvertToSubClassCode
  std::cout << "ConvertToSubClassCode EClass: " << sipCpp << std::endl;
  // sipType = sipType_DClass;
  if( dynamic_cast<DClass *>( sipCpp ) )
  {
    sipType = sipType_DClass;
    *sipCppRet = dynamic_cast<DClass *>( sipCpp );
  }
  if( dynamic_cast<EClass *>( sipCpp ) )
  {
    sipType = sipType_EClass;
    *sipCppRet = dynamic_cast<EClass *>( sipCpp );
  }
%End

  DClass();
  virtual ~DClass();
  int d() const;
  void set_d( int x );
  void print_this_d();
};


class EClass : CClass, DClass
{
public:
%TypeHeaderCode
#include "testmodule2.h"
%End

  EClass();
  virtual ~EClass();
  void print_this_e();

};


AClass* get_a();
CClass* get_c();
DClass* get_d();
EClass* get_e();


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


/* --- Makefile --- */

PYTHON_INCLUDE=/usr/include/python2.7

# SIP_INCLUDE=/usr/include/python2.7
SIP_EXE=sip

SIP_INCLUDE=/usr/include/python2.7
# SIP_INCLUDE=/casa/build/sip/include/python2.7
# SIP_EXE=/casa/build/sip/bin/sip

all:    testmodule2.so testmodule.so

siptestmodule1part0.cpp: testmodule1.sip testmodule1.cpp testmodule1.h
    $(SIP_EXE) -j1 -c . testmodule1.sip

siptestmodule2part0.cpp: testmodule2.sip testmodule2.cpp testmodule2.h
testmodule1.h
    $(SIP_EXE) -j1 -c . testmodule2.sip

siptestmodulepart0.cpp: testmodule.sip testmodule1.cpp testmodule2.cpp
testmodule1.h testmodule2.h
    $(SIP_EXE) -j1 -c . testmodule.sip

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

testmodule2.so:    siptestmodule2part0.cpp testmodule1.so
    g++ -fPIC -shared -o testmodule2.so -I$(SIP_INCLUDE)
-I$(PYTHON_INCLUDE) siptestmodule2part0.cpp testmodule2.cpp testmodule1.so

testmodule.so:    siptestmodulepart0.cpp
    g++ -fPIC -shared -o testmodule.so -I$(SIP_INCLUDE) -I$(PYTHON_INCLUDE)
siptestmodulepart0.cpp testmodule1.cpp testmodule2.cpp

clean:
    rm testmodule.so testmodule1.so testmodule2.so siptestmodule1part0.cpp
sipAPItestmodule1.h sipAPItestmodule2.h siptestmodule2part0.cpp
siptestmodulepart0.cpp sipAPItestmodule.h
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190208/13b77766/attachment-0001.html>


More information about the PyQt mailing list