Issue 20458 - CRTP + pass alias to virtual member to mixin = runtime crash
Summary: CRTP + pass alias to virtual member to mixin = runtime crash
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P3 normal
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-12-19 19:18 UTC by Adam D. Ruppe
Modified: 2023-03-23 07:28 UTC (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Adam D. Ruppe 2019-12-19 19:18:35 UTC
Encountered this during my android/jni stuff. Easier to describe with code than with words:

----

mixin template Impl(T, string name){
        alias a = __traits(getMember, T, name); // comment this and it works
        static void impl() {
                auto t = new T();
                __traits(getMember, t, name)();
        }
}

class A(CRTP) {
        static foreach(memberName; __traits(derivedMembers, CRTP))
                mixin Impl!(CRTP, memberName);
}

class Foo : A!Foo {
        void test() {
                import core.stdc.stdio;
                printf("Success\n");
        }
}

void main() {
        Foo.impl();
}

----


Compiles successfully. Running it leads to crash.

Make `class Foo` into `final class Foo` and it works.

Put `final` on `void test` instead and it works here, but adding more members can result in the wrong method being called. Leads me to believe the virtual function slot gets confused.


Comment that `alias a = ....` line in there and it works. So the fundamental problem is related to the alias being present in the mixin template, even if never used.

So the alias of the virtual derived member appearing in the base class. However, I believe the use of the curiously-recurring template pattern is necessary to trigger the bug though since I've been unable to reproduce the crash without that.


This is the most I have been able to minimize it... the original version is:


---
mixin template Impl(T, alias a) {
        static void impl() {
                auto t = new T();
                __traits(getMember, t, __traits(identifier, a))();
        }
}

class A(CRTP) {
        static foreach(memberName; __traits(derivedMembers, CRTP))
                mixin Impl!(CRTP, __traits(getMember, CRTP, memberName));
}

class Foo : A!Foo {
        void test() {
                import core.stdc.stdio;
                printf("Success\n");
        }
}

void main() {
        Foo.impl();
}
---

And I think it is worth noting that one too: just *passing* the alias to the mixin template also results in the same behavior. It doesn't have to explicitly mixin the alias, as long as it is present there.
Comment 1 basile-z 2023-03-22 18:30:46 UTC
With assertions built in DMD your code triggers this : 
https://github.com/dlang/dmd/blob/498822ec8efb8e2b68e257b01fa1e097ec6e3a88/compiler/src/dmd/dsymbolsem.d#L5261, so the vtbl seems to be started already, probably with "test" but then is rebuild again, without
Comment 2 basile-z 2023-03-22 23:25:40 UTC
reduced further

```
mixin template Impl(T){
    alias a =  __traits(getMember, T, "test");
    //alias a = T.test; // no vtbl corruption with this alias
}

class A(T) {
    mixin Impl!T;
}

class Foo : A!Foo {
    void test() {}
}

void main() {
    (new Foo).test();
}    
```
Comment 3 basile-z 2023-03-23 00:33:38 UTC
The same corruption without the getMember trait:

```
mixin template Impl(T){
    alias a = T.test;       
    pragma(msg, typeof(a)); // launch dsymbolsema and corrupt vtbl
}

class A(T) {
    mixin Impl!T;
}

class Foo : A!Foo {
    void test() {}
}

void main() {
    (new Foo).test();
}   
```
Comment 4 basile-z 2023-03-23 07:28:49 UTC
Finally the generic reproduction

```
class Base
{
    alias a = Derived.test;
    pragma(msg, typeof(a)); // triggers the vtbl corruption
}

class Derived : Base
{
    void test(){}
}

void main()
{
    (new Derived).test();
}   
```