This bug is present in 2.072.0 and 2.072.1, but not on older compiler versions.
The following code, instead of calling bar(), gets the vtable of the A interface and calls foo(). It just happens when the unrelated template instance (tinst) is present somewhere and if T is a template struct that calls bar() from its destructor.
---
interface A { void foo(); }
interface B { void bar(); }
interface AB : A, B {}
class C : AB {
void foo() { assert(false, "Never called!"); }
void bar() {}
}
struct T() {
AB ab;
~this() {
ab.bar(); // not OK, calls foo();
}
}
T!() tinst; // NOTE: the destructor of tinst is never run!
void main()
{
T!() dst;
dst.ab = new C;
dst.ab.bar(); // OK, calls bar()
}
---
This is currently a blocker for the efforts of integrating the new revamped vibe.d core module with the rest of the library.
For whatever reason the ab in the ab.bar() call of the dtor isn't casted to B
(cast(B)dst.ab).bar(); // <- correct in main, offsets this (AB) by 8 byte
ab.bar(); // <- broken, uses this (AB) w/o offset which is A, thus methods of B are called on A's vtbl
should be
(cast(B)ab).bar();
So what happens is that the size finalization of aggregates (classes and structs) was moved to semantic2 in order to resolve various forward reference issues.
The module level
T!() tinst;
declaration already triggers a FunctionDeclaration::semantic3 for the dtor during Module::semantic(). IIRC this is done for any template instances.
What's missing is a call to ad.size/determineSize somewhere before determining the offset when the CastExp is optimized.
Comment #5 by github-bugzilla — 2016-12-29T16:59:03Z
Comment #8 by razvan.nitu1305 — 2018-12-27T12:02:45Z
Slightly modified example:
interface A { void foo(); }
interface B { void bar(); }
interface AB : A, B {}
class C : AB {
void foo() { assert(false, "Never called!"); }
void bar() {}
}
struct T() {
AB ab;
~this() {
ab.bar(); // not OK, calls foo();
}
}
static ~this() // calling the destructor still fails
{
tinst.__dtor();
}
T!() tinst; // NOTE: the destructor of tinst is never run!
void main()
{
T!() dst;
dst.ab = new C;
dst.ab.bar(); // OK, calls bar()
}
Comment #9 by sahmi.soulaimane — 2019-05-25T08:01:29Z
> // calling the destructor still fails
It fails because the field `ab` is not initialized, hence holds `null`. Try declaring `tinst` like this:
```
auto tinst = T!()(new C);
```