Skip to content

Virtual inheritance #651

@HGuillemet

Description

@HGuillemet

In the JNI code, the address field of Java objects is interpreted as a pointer to a C++ object using a C-style cast.
This always works when the effective class of the object is the class we are casting to. This works also in most cases where the object class derived. But not always.

First case where this cast is illegal is when the base class B is polymorphic and the derived class D inherits virtually from B:

Example 1:

#include <cstdio>

class B {
  int b = 1;
  public:
    virtual void f() { printf("%d\n", b); }
};

class D: public virtual B {
  int d = 2;
};

If we write in Java: new D().f() we get a segmentation fault.
To prevent this, we would need to replace the C-style cast by a static_cast<B*>(ptr) where ptr is a D*, but we never have a chance to. First time we enter C++ code during this call is in the JNI of method f and at this point the code is specific to B and knows nothing about D.

Example 2, we add this function to Example 1:

void g(B b) {
  b.f();
}

When I call from Java global.g(new D()), on my machine I get 2, which is incorrect. Others may get segmentation fault. Anyway the C-style cast (B*) made by the JNI of g on its argument is illegal.

Another case where C-style cast is illegal is multiple inheritance.

Example 3, we add:

class B2 {
  int b2 = 3;
};

class D2: public B, public B2 {
  int d2 = 4;
};

Here, no segmentation fault nor incorrect output when we call new D2().f() or global.g(new D2()). Java doesn't support multiple inheritance and JavaCPP only keeps the first inheritance relationship. I doubt this is written in any specification, but in practice C-style casts works to up-cast to the first class. JavaCPP also produces automatically an asB2() instance method for D2 that does a static_cast<B2*>.

My suggestions:

  1. Drop the Java inheritance in case of C++ virtual inheritance of a polymorphic class. The Java inheritance is just useless and only causes segmentation faults or, worse, hard to diagnose erroneous results. I don't think there is a way to have Java inheritance works here. Is there ?
  2. Add an automatic asB() method in this case, just like it's done for multiple inheritance, and also manually by Info in Pytorch presets for Module subclasses.
  3. In addition to asB(), we could also automatically remap all member functions of B in D. In example 1, we would add a D.f native method. This would allow the user to do d.f() like in C++ instead of d.asB().f(). Similarly, we could add overloads for all methods taking a B (or one of its superclass) as argument, that take a D instead. This is done manually by Info in Pytorch too, in the specific case of the register_module method.

Suggestions 1 and 2 are important I believe.
3 is less essential, and I'm not sure there is an easy way to implement this in the parser.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions