Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different behaviour of default interface impelmentation in monoVM vs CoreCLR #81882

Closed
michaldobrodenka opened this issue Feb 9, 2023 · 9 comments

Comments

@michaldobrodenka
Copy link

michaldobrodenka commented Feb 9, 2023

Description

In net7 when using monoVM different method is called.

Reproduction Steps

This small example:

    interface IDefault
    {
        public void Method1()
        {
            Console.WriteLine("Interface Method1");
        }

        public void Method2()
        {
            Console.WriteLine("Interface Method2");
        }
    }

    abstract class ClassA : IDefault
    {
        virtual public void Method1()
        {
            Console.WriteLine("ClassA Method 1");
        }
    }

    class ClassB : ClassA
    {
        virtual public void Method2()
        {
            Console.WriteLine("ClassB Method2");
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            IDefault c = new ClassB();

            c.Method1();
            c.Method2();
        }
    }

Expected behavior

the same methods to be called

Actual behavior

For CoreCLR outputs:
ClassA Method 1
ClassB Method2

When using monoVM:
ClassA Method 1
Interface Method2

Regression?

no

Known Workarounds

not to use default interface implementatinos

Configuration

Tested on:

net7/linux/armv6 (only supports monoVM so this is where I found it)
net7/win/x64 when publishing app with dotnet publish --self-contained true -p:UseMonoRuntime=true
net7-android

Other information

No response

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Feb 9, 2023
@michaldobrodenka
Copy link
Author

michaldobrodenka commented Feb 9, 2023

smallest example csproj

ConsoleApp4(1).zip

@michaldobrodenka
Copy link
Author

Only when ClassA is abstract!

@janvorli
Copy link
Member

janvorli commented Feb 9, 2023

cc: @trylek - maybe that's what you've fixed recently?

@akoeplinger
Copy link
Member

/cc @lambdageek @vargaz

@lambdageek lambdageek added area-VM-meta-mono and removed untriaged New issue has not been triaged by the area owner labels Feb 9, 2023
@lambdageek lambdageek added this to the 8.0.0 milestone Feb 9, 2023
@lambdageek
Copy link
Member

lambdageek commented Feb 9, 2023

vtable debug log
*** Vtable for class 'ClassB' at "AFTER INHERITING PARENT VTABLE" (size 7)
Packed interface table for class ClassB has size 1
  [000][UUID 622][SLOT 004][SIZE  002] interface IDefault
Interface flags: (0,F)(1,F)(2,F)(3,F)(4,F)(5,F)(6,F)(7,F)(8,F)(9,F)(10,F)(11,F)(12,F)(13,F)(14,F)(15,F)(16,F)(17,F)(18,F)(19,F)(20,F)(21,F)(22,F)(23,F)(24,F)(25,F)(26,F)(27,F)(28,F)(29,F)(30,F)(31,F)(32,F)(33,F)(34,F)(35,F)(36,F)(37,F)(38,F)(39,F)(40,F)(41,F)(42,F)(43,F)(44,F)(45,F)(46,F)(47,F)(48,F)(49,F)(50,F)(51,F)(52,F)(53,F)(54,F)(55,F)(56,F)(57,F)(58,F)(59,F)(60,F)(61,F)(62,F)(63,F)(64,F)(65,F)(66,F)(67,F)(68,F)(69,F)(70,F)(71,F)(72,F)(73,F)(74,F)(75,F)(76,F)(77,F)(78,F)(79,F)(80,F)(81,F)(82,F)(83,F)(84,F)(85,F)(86,F)(87,F)(88,F)(89,F)(90,F)(91,F)(92,F)(93,F)(94,F)(95,F)(96,F)(97,F)(98,F)(99,F)(100,F)(101,F)(102,F)(103,F)(104,F)(105,F)(106,F)(107,F)(108,F)(109,F)(110,F)(111,F)(112,F)(113,F)(114,F)(115,F)(116,F)(117,F)(118,F)(119,F)(120,F)(121,F)(122,F)(123,F)(124,F)(125,F)(126,F)(127,F)(128,F)(129,F)(130,F)(131,F)(132,F)(133,F)(134,F)(135,F)(136,F)(137,F)(138,F)(139,F)(140,F)(141,F)(142,F)(143,F)(144,F)(145,F)(146,F)(147,F)(148,F)(149,F)(150,F)(151,F)(152,F)(153,F)(154,F)(155,F)(156,F)(157,F)(158,F)(159,F)(160,F)(161,F)(162,F)(163,F)(164,F)(165,F)(166,F)(167,F)(168,F)(169,F)(170,F)(171,F)(172,F)(173,F)(174,F)(175,F)(176,F)(177,F)(178,F)(179,F)(180,F)(181,F)(182,F)(183,F)(184,F)(185,F)(186,F)(187,F)(188,F)(189,F)(190,F)(191,F)(192,F)(193,F)(194,F)(195,F)(196,F)(197,F)(198,F)(199,F)(200,F)(201,F)(202,F)(203,F)(204,F)(205,F)(206,F)(207,F)(208,F)(209,F)(210,F)(211,F)(212,F)(213,F)(214,F)(215,F)(216,F)(217,F)(218,F)(219,F)(220,F)(221,F)(222,F)(223,F)(224,F)(225,F)(226,F)(227,F)(228,F)(229,F)(230,F)(231,F)(232,F)(233,F)(234,F)(235,F)(236,F)(237,F)(238,F)(239,F)(240,F)(241,F)(242,F)(243,F)(244,F)(245,F)(246,F)(247,F)(248,F)(249,F)(250,F)(251,F)(252,F)(253,F)(254,F)(255,F)(256,F)(257,F)(258,F)(259,F)(260,F)(261,F)(262,F)(263,F)(264,F)(265,F)(266,F)(267,F)(268,F)(269,F)(270,F)(271,F)(272,F)(273,F)(274,F)(275,F)(276,F)(277,F)(278,F)(279,F)(280,F)(281,F)(282,F)(283,F)(284,F)(285,F)(286,F)(287,F)(288,F)(289,F)(290,F)(291,F)(292,F)(293,F)(294,F)(295,F)(296,F)(297,F)(298,F)(299,F)(300,F)(301,F)(302,F)(303,F)(304,F)(305,F)(306,F)(307,F)(308,F)(309,F)(310,F)(311,F)(312,F)(313,F)(314,F)(315,F)(316,F)(317,F)(318,F)(319,F)(320,F)(321,F)(322,F)(323,F)(324,F)(325,F)(326,F)(327,F)(328,F)(329,F)(330,F)(331,F)(332,F)(333,F)(334,F)(335,F)(336,F)(337,F)(338,F)(339,F)(340,F)(341,F)(342,F)(343,F)(344,F)(345,F)(346,F)(347,F)(348,F)(349,F)(350,F)(351,F)(352,F)(353,F)(354,F)(355,F)(356,F)(357,F)(358,F)(359,F)(360,F)(361,F)(362,F)(363,F)(364,F)(365,F)(366,F)(367,F)(368,F)(369,F)(370,F)(371,F)(372,F)(373,F)(374,F)(375,F)(376,F)(377,F)(378,F)(379,F)(380,F)(381,F)(382,F)(383,F)(384,F)(385,F)(386,F)(387,F)(388,F)(389,F)(390,F)(391,F)(392,F)(393,F)(394,F)(395,F)(396,F)(397,F)(398,F)(399,F)(400,F)(401,F)(402,F)(403,F)(404,F)(405,F)(406,F)(407,F)(408,F)(409,F)(410,F)(411,F)(412,F)(413,F)(414,F)(415,F)(416,F)(417,F)(418,F)(419,F)(420,F)(421,F)(422,F)(423,F)(424,F)(425,F)(426,F)(427,F)(428,F)(429,F)(430,F)(431,F)(432,F)(433,F)(434,F)(435,F)(436,F)(437,F)(438,F)(439,F)(440,F)(441,F)(442,F)(443,F)(444,F)(445,F)(446,F)(447,F)(448,F)(449,F)(450,F)(451,F)(452,F)(453,F)(454,F)(455,F)(456,F)(457,F)(458,F)(459,F)(460,F)(461,F)(462,F)(463,F)(464,F)(465,F)(466,F)(467,F)(468,F)(469,F)(470,F)(471,F)(472,F)(473,F)(474,F)(475,F)(476,F)(477,F)(478,F)(479,F)(480,F)(481,F)(482,F)(483,F)(484,F)(485,F)(486,F)(487,F)(488,F)(489,F)(490,F)(491,F)(492,F)(493,F)(494,F)(495,F)(496,F)(497,F)(498,F)(499,F)(500,F)(501,F)(502,F)(503,F)(504,F)(505,F)(506,F)(507,F)(508,F)(509,F)(510,F)(511,F)(512,F)(513,F)(514,F)(515,F)(516,F)(517,F)(518,F)(519,F)(520,F)(521,F)(522,F)(523,F)(524,F)(525,F)(526,F)(527,F)(528,F)(529,F)(530,F)(531,F)(532,F)(533,F)(534,F)(535,F)(536,F)(537,F)(538,F)(539,F)(540,F)(541,F)(542,F)(543,F)(544,F)(545,F)(546,F)(547,F)(548,F)(549,F)(550,F)(551,F)(552,F)(553,F)(554,F)(555,F)(556,F)(557,F)(558,F)(559,F)(560,F)(561,F)(562,F)(563,F)(564,F)(565,F)(566,F)(567,F)(568,F)(569,F)(570,F)(571,F)(572,F)(573,F)(574,F)(575,F)(576,F)(577,F)(578,F)(579,F)(580,F)(581,F)(582,F)(583,F)(584,F)(585,F)(586,F)(587,F)(588,F)(589,F)(590,F)(591,F)(592,F)(593,F)(594,F)(595,F)(596,F)(597,F)(598,F)(599,F)(600,F)(601,F)(602,F)(603,F)(604,F)(605,F)(606,F)(607,F)(608,F)(609,F)(610,F)(611,F)(612,F)(613,F)(614,F)(615,F)(616,F)(617,F)(618,F)(619,F)(620,F)(621,F)(622,T)
Dump interface flags: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40
[LEVEL 0] Implemented interfaces by class ClassB:
[LEVEL 1] Implemented interfaces by class ClassA:
  [UIID 622] interface IDefault
  [000][UUID 622][SLOT 004][SIZE  002] interface .IDefault
[LEVEL 2] Implemented interfaces by class Object:
* Interfaces for class 'ClassB' done.
Starting vtable (size 7):
  [O][000][INDEX 000] object:GetHashCode () [0x12701ac30]
  [O][001][INDEX 001] object:Equals (object) [0x12701ac08]
  [O][002][INDEX 002] object:ToString () [0x12701abe0]
  [O][003][INDEX 003] object:Finalize () [0x12701abb8]
  [O][004][INDEX 006] ClassA:Method1 () [0x1370678b8]
  [O][005][INDEX 001] IDefault:Method2 () [0x137067890]
  [O][006][INDEX 006] ClassA:Method1 () [0x1370678b8]
Override map "AFTER OVERRIDING INTERFACE METHODS" EMPTY.
*** Vtable for class 'ClassB' at "AFTER OVERRIDING INTERFACE METHODS" (size 7)
  [O][000][INDEX 000] object:GetHashCode () [0x12701ac30]
  [O][001][INDEX 001] object:Equals (object) [0x12701ac08]
  [O][002][INDEX 002] object:ToString () [0x12701abe0]
  [O][003][INDEX 003] object:Finalize () [0x12701abb8]
  [O][004][INDEX 006] ClassA:Method1 () [0x1370678b8]
  [O][005][INDEX 001] IDefault:Method2 () [0x137067890]
  [O][006][INDEX 006] ClassA:Method1 () [0x1370678b8]
	checking iface method IDefault:Method1 ()
    For slot 4 (''.'IDefault':'Method1'), trying method ''.'ClassB':'Method2'... [EXPLICIT IMPLEMENTATION = 0][SLOT IS NULL = 0][RANK CHECK FAILED]
	checking iface method IDefault:Method2 ()
    For slot 5 (''.'IDefault':'Method2'), trying method ''.'ClassB':'Method2'... [EXPLICIT IMPLEMENTATION = 0][SLOT IS NULL = 0][NOT EXPLICIT IMPLEMENTATION IN FULL SLOT REFUSED]
      [class is not abstract, checking slot 4 for interface ''.'IDefault', method Method1, slot check is 0]
      [class is not abstract, checking slot 5 for interface ''.'IDefault', method Method2, slot check is 0]
*** Vtable for class 'ClassB' at "AFTER SETTING UP INTERFACE METHODS" (size 7)
  [O][000][INDEX 000] object:GetHashCode () [0x12701ac30]
  [O][001][INDEX 001] object:Equals (object) [0x12701ac08]
  [O][002][INDEX 002] object:ToString () [0x12701abe0]
  [O][003][INDEX 003] object:Finalize () [0x12701abb8]
  [O][004][INDEX 006] ClassA:Method1 () [0x1370678b8]
  [O][005][INDEX 001] IDefault:Method2 () [0x137067890]
  [O][006][INDEX 006] ClassA:Method1 () [0x1370678b8]
*** Vtable for class 'ClassB' at "AFTER OVERRIDING NON-INTERFACE METHODS" (size 8)
  [O][000][INDEX 000] object:GetHashCode () [0x12701ac30]
  [O][001][INDEX 001] object:Equals (object) [0x12701ac08]
  [O][002][INDEX 002] object:ToString () [0x12701abe0]
  [O][003][INDEX 003] object:Finalize () [0x12701abb8]
  [O][004][INDEX 006] ClassA:Method1 () [0x1370678b8]
  [O][005][INDEX 001] IDefault:Method2 () [0x137067890]
  [O][006][INDEX 006] ClassA:Method1 () [0x1370678b8]
  [N][007][INDEX 007] ClassB:Method2 () [0x1370678e0]
*** Vtable for class 'ClassB' at "FINALLY" (size 8)
  [O][000][INDEX 000] object:GetHashCode () [0x12701ac30]
  [O][001][INDEX 001] object:Equals (object) [0x12701ac08]
  [O][002][INDEX 002] object:ToString () [0x12701abe0]
  [O][003][INDEX 003] object:Finalize () [0x12701abb8]
  [O][004][INDEX 006] ClassA:Method1 () [0x1370678b8]
  [O][005][INDEX 001] IDefault:Method2 () [0x137067890]
  [O][006][INDEX 006] ClassA:Method1 () [0x1370678b8]
  [N][007][INDEX 007] ClassB:Method2 () [0x1370678e0]

The relevant bit is:

    For slot 5 (''.'IDefault':'Method2'), trying method ''.'ClassB':'Method2'... [EXPLICIT IMPLEMENTATION = 0][SLOT IS NULL = 0][NOT EXPLICIT IMPLEMENTATION IN FULL SLOT REFUSED]

So we didn't like ClassB.Method2 because IDefault is inherited from ClassA, not explicitly implemented by ClassB.

ClassB.Method2 is always .method public hidebysig newslot virtual instance. So when ClassA is not abstract, or when DIMs are not in the picture at all, this makes sense to me... we have a newslot method that happens to have the same name as something that our parent already implemented. We can ignore it.

But I guess if the parent (ancestor?) is abstract then we do want to override the DIM? I think I need to read the ecma augments...

@trylek
Copy link
Member

trylek commented Feb 9, 2023

@janvorli - I don't think I've been fixing anything in this area, please note this issue deals with instance, not static virtual methods, I've only been making changes to the static path. One way or another, the reabstraction fix you reviewed about a week or two ago still hasn't been merged in because it fails in several Mono legs, I'm working with the Mono team on figuring out the fix.

FWIW, I'm somewhat surprised by the fact that the method overrides in ClassA and ClassB are marked as virtual, not as override as I'd expect, but perhaps that's just my incomplete knowledge of the C# syntax.

@lambdageek
Copy link
Member

I think the issue is we only want to fill the vtable slot if the current class is not abstract. That is, classA's Method2 slot should be empty, and class B should fill it in with the DIM method as the last step.

if (vtable [im_slot] == NULL) {
if (!(im->flags & METHOD_ATTRIBUTE_ABSTRACT)) {
TRACE_INTERFACE_VTABLE (printf (" Using default iface method %s.\n", mono_method_full_name (im, 1)));
vtable [im_slot] = im;
}

PR shortly, after I validate locally that this doesn't break some other cases, to avoid public embarrassment.

lambdageek added a commit to lambdageek/runtime that referenced this issue Feb 23, 2023
leave the slot NULL and allow leaf classes to fill it in.

Related to dotnet#81882
lambdageek added a commit that referenced this issue Feb 28, 2023
)

* [class]  treat slot as empty if it's filled in with a DIM

   Classes may override an interface method slot if it was filled in with a DIM, just as if it was empty.

Related to #81882
@lambdageek
Copy link
Member

Fixed in #82556

@ghost ghost locked as resolved and limited conversation to collaborators Apr 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants