bytedeco/javacpp

Virtual inheritance

Open

#651 opened on Feb 13, 2023

View on GitHub
 (9 comments) (0 reactions) (0 assignees)Java (620 forks)batch import
enhancementhelp wanted

Repository metrics

Stars
 (4,279 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

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.

Contributor guide