runtime: Runtime incorrectly interprets methodimpl entries for class overrides
The runtime incorrectly interprets methodimpl table entries for overrides of class methods. Since they are used for generating code for covariant returns, we will need the runtime to obey them in order to correctly execute code using covariant returns. However, the problem can be illustrated without covariant returns using a compiler from Roslyn’s covariant return feature branch.
The following program produces the IL shown below it. Running the program produces the (incorrect) output shown afterwards. The first two lines in each group of output lines are wrong.
using System;
using System.Collections.Generic;
abstract class Base<T, U>
{
public virtual void Method(out List<U> y, ref List<T> x) { y = null; Console.WriteLine("Base<T, U>.Method(out List<U> y, ref List<T> x)"); }
public virtual void Method(ref List<T> x, out List<U> y) { y = null; Console.WriteLine("Base<T, U>.Method(ref List<T> x, out List<U> y)"); }
public virtual void Method(ref List<U> x) { Console.WriteLine("Base<T, U>.Method(ref List<U> x)"); }
}
class Base2<A, B> : Base<A, B>
{
public virtual void Method(out List<A> x)
{
x = null;
Console.WriteLine("Base2<A, B>.Method(out List<A> x)");
}
}
class Derived : Base2<int, int>
{
public override void Method(ref List<int> a, out List<int> b) // No warning - the compiler produces a methodimpl record to disambiguate
{
b = null;
Console.WriteLine("Derived.Method(ref List<int> a, out List<int> b)");
}
public override void Method(ref List<int> a) // No warning when ambiguous signatures are spread across multiple base types
{
Console.WriteLine("Derived.Method(ref List<int> a)");
}
}
class Program
{
static void Main()
{
List<int> t = null;
Derived d = new Derived();
d.Method(ref t, out t);
d.Method(out t, ref t);
d.Method(ref t);
d.Method(out t);
Console.WriteLine();
Base2<int, int> b2 = d;
b2.Method(ref t, out t);
b2.Method(out t, ref t);
b2.Method(ref t);
b2.Method(out t);
Console.WriteLine();
Base<int, int> b1 = d;
b1.Method(ref t, out t);
b1.Method(out t, ref t);
b1.Method(ref t);
}
}
// Microsoft (R) .NET Framework IL Disassembler. Version 4.8.3928.0
// Copyright (c) Microsoft Corporation. All rights reserved.
// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly Program
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module Program.exe
// MVID: {C6C266E9-7949-4242-B429-F9011402D996}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x04F20000
// =============== CLASS MEMBERS DECLARATION ===================
.class private abstract auto ansi beforefieldinit Base`2<T,U>
extends [mscorlib]System.Object
{
.method public hidebysig newslot virtual
instance void Method([out] class [mscorlib]System.Collections.Generic.List`1<!U>& y,
class [mscorlib]System.Collections.Generic.List`1<!T>& x) cil managed
{
// Code size 16 (0x10)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldnull
IL_0003: stind.ref
IL_0004: ldstr "Base<T, U>.Method(out List<U> y, ref List<T> x)"
IL_0009: call void [mscorlib]System.Console::WriteLine(string)
IL_000e: nop
IL_000f: ret
} // end of method Base`2::Method
.method public hidebysig newslot virtual
instance void Method(class [mscorlib]System.Collections.Generic.List`1<!T>& x,
[out] class [mscorlib]System.Collections.Generic.List`1<!U>& y) cil managed
{
// Code size 16 (0x10)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.2
IL_0002: ldnull
IL_0003: stind.ref
IL_0004: ldstr "Base<T, U>.Method(ref List<T> x, out List<U> y)"
IL_0009: call void [mscorlib]System.Console::WriteLine(string)
IL_000e: nop
IL_000f: ret
} // end of method Base`2::Method
.method public hidebysig newslot virtual
instance void Method(class [mscorlib]System.Collections.Generic.List`1<!U>& x) cil managed
{
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Base<T, U>.Method(ref List<U> x)"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Base`2::Method
.method family hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Base`2::.ctor
} // end of class Base`2
.class private auto ansi beforefieldinit Base2`2<A,B>
extends class Base`2<!A,!B>
{
.method public hidebysig newslot virtual
instance void Method([out] class [mscorlib]System.Collections.Generic.List`1<!A>& x) cil managed
{
// Code size 16 (0x10)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldnull
IL_0003: stind.ref
IL_0004: ldstr "Base2<A, B>.Method(out List<A> x)"
IL_0009: call void [mscorlib]System.Console::WriteLine(string)
IL_000e: nop
IL_000f: ret
} // end of method Base2`2::Method
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void class Base`2<!A,!B>::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Base2`2::.ctor
} // end of class Base2`2
.class private auto ansi beforefieldinit Derived
extends class Base2`2<int32,int32>
{
.method public hidebysig newslot virtual
instance void Method(class [mscorlib]System.Collections.Generic.List`1<int32>& a,
[out] class [mscorlib]System.Collections.Generic.List`1<int32>& b) cil managed
{
.override method instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!0>&,
class [mscorlib]System.Collections.Generic.List`1<!1>&)
// Code size 16 (0x10)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.2
IL_0002: ldnull
IL_0003: stind.ref
IL_0004: ldstr "Derived.Method(ref List<int> a, out List<int> b)"
IL_0009: call void [mscorlib]System.Console::WriteLine(string)
IL_000e: nop
IL_000f: ret
} // end of method Derived::Method
.method public hidebysig newslot virtual
instance void Method(class [mscorlib]System.Collections.Generic.List`1<int32>& a) cil managed
{
.override method instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!1>&)
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Derived.Method(ref List<int> a)"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Derived::Method
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void class Base2`2<int32,int32>::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Derived::.ctor
} // end of class Derived
.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 137 (0x89)
.maxstack 3
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> t,
[1] class Derived d,
[2] class Base2`2<int32,int32> b2,
[3] class Base`2<int32,int32> b1)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: newobj instance void Derived::.ctor()
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000a: ldloca.s t
IL_000c: ldloca.s t
IL_000e: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!0>&,
class [mscorlib]System.Collections.Generic.List`1<!1>&)
IL_0013: nop
IL_0014: ldloc.1
IL_0015: ldloca.s t
IL_0017: ldloca.s t
IL_0019: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!1>&,
class [mscorlib]System.Collections.Generic.List`1<!0>&)
IL_001e: nop
IL_001f: ldloc.1
IL_0020: ldloca.s t
IL_0022: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!1>&)
IL_0027: nop
IL_0028: ldloc.1
IL_0029: ldloca.s t
IL_002b: callvirt instance void class Base2`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!0>&)
IL_0030: nop
IL_0031: call void [mscorlib]System.Console::WriteLine()
IL_0036: nop
IL_0037: ldloc.1
IL_0038: stloc.2
IL_0039: ldloc.2
IL_003a: ldloca.s t
IL_003c: ldloca.s t
IL_003e: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!0>&,
class [mscorlib]System.Collections.Generic.List`1<!1>&)
IL_0043: nop
IL_0044: ldloc.2
IL_0045: ldloca.s t
IL_0047: ldloca.s t
IL_0049: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!1>&,
class [mscorlib]System.Collections.Generic.List`1<!0>&)
IL_004e: nop
IL_004f: ldloc.2
IL_0050: ldloca.s t
IL_0052: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!1>&)
IL_0057: nop
IL_0058: ldloc.2
IL_0059: ldloca.s t
IL_005b: callvirt instance void class Base2`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!0>&)
IL_0060: nop
IL_0061: call void [mscorlib]System.Console::WriteLine()
IL_0066: nop
IL_0067: ldloc.1
IL_0068: stloc.3
IL_0069: ldloc.3
IL_006a: ldloca.s t
IL_006c: ldloca.s t
IL_006e: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!0>&,
class [mscorlib]System.Collections.Generic.List`1<!1>&)
IL_0073: nop
IL_0074: ldloc.3
IL_0075: ldloca.s t
IL_0077: ldloca.s t
IL_0079: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!1>&,
class [mscorlib]System.Collections.Generic.List`1<!0>&)
IL_007e: nop
IL_007f: ldloc.3
IL_0080: ldloca.s t
IL_0082: callvirt instance void class Base`2<int32,int32>::Method(class [mscorlib]System.Collections.Generic.List`1<!1>&)
IL_0087: nop
IL_0088: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
} // end of class Program
// =============================================================
// *********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file Program.res
Output produced by .net 3.1:
Base<T, U>.Method(ref List<T> x, out List<U> y)
Derived.Method(ref List<int> a, out List<int> b)
Derived.Method(ref List<int> a)
Base2<A, B>.Method(out List<A> x)
Base<T, U>.Method(ref List<T> x, out List<U> y)
Derived.Method(ref List<int> a, out List<int> b)
Derived.Method(ref List<int> a)
Base2<A, B>.Method(out List<A> x)
Base<T, U>.Method(ref List<T> x, out List<U> y)
Derived.Method(ref List<int> a, out List<int> b)
Derived.Method(ref List<int> a)
The correct output should be:
Derived.Method(ref List<int> a, out List<int> b)
Base<T, U>.Method(out List<U> y, ref List<T> x)
Derived.Method(ref List<int> a)
Base2<A, B>.Method(out List<A> x)
Derived.Method(ref List<int> a, out List<int> b)
Base<T, U>.Method(out List<U> y, ref List<T> x)
Derived.Method(ref List<int> a)
Base2<A, B>.Method(out List<A> x)
Derived.Method(ref List<int> a, out List<int> b)
Base<T, U>.Method(out List<U> y, ref List<T> x)
Derived.Method(ref List<int> a)
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 17 (16 by maintainers)
Commits related to this issue
- Changes per review comments - Revert behavior of PEMethodSymbol.IsOverride (true even if we cannot report a unique overridden method) - Add runtime test for behavior of methodimpl in ambiguous scenari... — committed to gafter/roslyn by deleted user 4 years ago
- Condition WRN_MultipleRuntimeOverrideMatches on whether runtime has working methodimpl Relates to https://github.com/dotnet/runtime/issues/38119 Fixes https://github.com/dotnet/roslyn/issues/45453 — committed to gafter/roslyn by deleted user 4 years ago
- Covariant returns part 4 (#44025) * Implement and test retargeting symbols for covariant returns. * A PE symbol with multiple methodimpl entries is ignored as an explicit override for language covar... — committed to dotnet/roslyn by deleted user 4 years ago
Fixed with PR #38310
Please also check whether mono produces the correct result when the order of the methods in Base are changed.