www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Bug? NVI functions can't call normal interface functions?

reply Johannes Pfau <nospam example.com> writes:
import core.stdc.stdio;
interface Timer
{
    final int run() { printf("Timer.run()\n"); fun(); return 1; };
    int fun();
}
interface Application
{
    final int run() { printf("Application.run()\n"); fun(); return 2; };
    int fun();
}

class TimedApp : Timer, Application
{
    int fun()
    {
        printf("TimedApp.fun()\n");
        return 0;
    }
}

void main()
{
    auto app = new TimedApp;
    (cast(Application)app).run();
    (cast(Timer)app).run();
    app.Application.run();
    app.Timer.run();
}

Output:
----
Application.run()
TimedApp.fun()
Timer.run()
TimedApp.fun()
Application.run()
Timer.run()
----

Note how "TimedApp.fun()" is called if cast was used, but not if
app.*Interface*. was called. I guess this is a bug?
Mar 17 2013
next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
Yes, this is a bug.

The funny thing here is that instead TimedApp.fun(), 
object.Object.toString() is called due to shift error in virtual 
table offset jumping. I guess due to interfaces' ABI a class 
instance isn't passed properly. Using

     override string toString()
     {
         printf("bzzzz\n");
         return "";
     }

reveals the bug. Valgrind also doesn't detect error like this. 
Vtable storage layout may explain where bug comes from:

_D4main8TimedApp6__vtblZ:
	dd	offset FLAT:_D4main8TimedApp7__ClassZ 64
	db	000h,000h,000h,000h	;....
	dd	offset FLAT:_D6object6Object8toStringMFZAya 64
	db	000h,000h,000h,000h	;....
	dd	offset FLAT:_D6object6Object6toHashMFNbNeZm 64
	db	000h,000h,000h,000h	;....
	dd	offset FLAT:_D6object6Object5opCmpMFC6ObjectZi 64
	db	000h,000h,000h,000h	;....
	dd	offset FLAT:_D6object6Object8opEqualsMFC6ObjectZb 64
	db	000h,000h,000h,000h	;....
	dd	offset FLAT:_D6object6Object8opEqualsMFC6ObjectC6ObjectZb 64
	db	000h,000h,000h,000h	;....
	dd	offset FLAT:_D4main8TimedApp3funMFZi 64
	db	000h,000h,000h,000h	;....


I remember Don was posting similar issue but do not remember the 
#.
Mar 17 2013
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
Actually it was http://d.puremagic.com/issues/show_bug.cgi?id=4589
Mar 17 2013
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 17 Mar 2013 09:59:03 -0400, Maxim Fomin <maxim maxim-fomin.ru>  
wrote:

 Actually it was http://d.puremagic.com/issues/show_bug.cgi?id=4589

I don't think this is the same problem. I modified the program a bit: import core.stdc.stdio; interface Timer { final int run() { printf("Timer.run(), this=%x\n", cast(void*)this); fun(); return 1;} int fun(); } interface Application { final int run() { printf("Application.run(), this=%x\n", cast(void *)this); fun(); return 2; }; int fun(); } class TimedApp : Timer, Application { int fun() { printf("TimedApp.fun(), this=%x\n", cast(void*)this); return 0; } } void main() { auto app = new TimedApp; (cast(Application)app).run(); (cast(Timer)app).run(); app.Application.run(); app.Timer.run(); } New output: Application.run(), this=10007ff8 TimedApp.fun(), this=10007fe0 Timer.run(), this=10007ff0 TimedApp.fun(), this=10007fe0 Application.run(), this=10007fe0 Timer.run(), this=10007fe0 Note that Application.run() is given the interface pointer 10007ff8, whereas the *true* object pointer is 10007fe0. This is normal and expected. However, in the *last* call to Application.run (and Timer.run), it's given the pointer 10007fe0, the true object pointer. This gives it the completely WRONG vtable to use for calling fun. I think this is different than the bug I posted. It may be related, I don't know. If this bug is not already reported, it should be. -Steve
Mar 19 2013
prev sibling parent Johannes Pfau <nospam example.com> writes:
Am Tue, 19 Mar 2013 17:51:52 -0400
schrieb "Steven Schveighoffer" <schveiguy yahoo.com>:

 On Sun, 17 Mar 2013 09:59:03 -0400, Maxim Fomin
 <maxim maxim-fomin.ru> wrote:
 
 Actually it was http://d.puremagic.com/issues/show_bug.cgi?id=4589

I don't think this is the same problem. I modified the program a bit: import core.stdc.stdio; interface Timer { final int run() { printf("Timer.run(), this=%x\n", cast(void*)this); fun(); return 1;} int fun(); } interface Application { final int run() { printf("Application.run(), this=%x\n", cast(void *)this); fun(); return 2; }; int fun(); } class TimedApp : Timer, Application { int fun() { printf("TimedApp.fun(), this=%x\n", cast(void*)this); return 0; } } void main() { auto app = new TimedApp; (cast(Application)app).run(); (cast(Timer)app).run(); app.Application.run(); app.Timer.run(); } New output: Application.run(), this=10007ff8 TimedApp.fun(), this=10007fe0 Timer.run(), this=10007ff0 TimedApp.fun(), this=10007fe0 Application.run(), this=10007fe0 Timer.run(), this=10007fe0 Note that Application.run() is given the interface pointer 10007ff8, whereas the *true* object pointer is 10007fe0. This is normal and expected. However, in the *last* call to Application.run (and Timer.run), it's given the pointer 10007fe0, the true object pointer. This gives it the completely WRONG vtable to use for calling fun. I think this is different than the bug I posted. It may be related, I don't know. If this bug is not already reported, it should be. -Steve

I'll report it soon and I already have a bug fix. (The compiler indeed always passed the object pointer. This is valid for base classes, but not for interfaces. Adding an isInterface check + CastExpression solves this issue)
Mar 20 2013