It seems I errantly blamed ILMerge for those PEVerify errors. At worst it may be a C# compiler optimization gone awry or it may be that PEVerify is not smart enough.
The Source
Consider this code:
public IList<ITestCommand> Children
{
get { return children ?? (IList<ITestCommand>)EmptyArray<ITestCommand>.Instance; }
}
The left-hand side of the null-coalescing operator has type: List<ITestCommand>.
The right-hand side of the null-coalescing operator has type: ITestCommand[] before the cast.
The result has type: IList<ITestCommand>.
So we need a little silent cast in there to make the types match up on either side. Really, it's just providing a compatible upper bound to which both sides can be converted. At runtime, this operation is a no-op. Technically it's something that the compiler itself could have inferred if it were smart enough.
The Unoptimized IL
Let's take a quick look at the IL that the C# compiler generates for a standard unoptimized build.
Notice that the castclass operator appears in the listing. There is also quite a bit of jiggery-pokery involving a compiler generated temporary local variable.
.method public hidebysig specialname newslot virtual final instance class [mscorlib]System.Collections.Generic.IList`1<class Gallio.Model.Execution.ITestCommand> get_Children() cil managed { .maxstack 2 .locals init ( [0] class [mscorlib]System.Collections.Generic.IList`1<class Gallio.Model.Execution.ITestCommand> CS$1$0000) L_0000: nop L_0001: ldarg.0 L_0002: ldfld class [mscorlib]System.Collections.Generic.List`1<class Gallio.Model.Execution.ITestCommand> Gallio.Model.Execution.ManagedTestCommand::children L_0007: dup L_0008: brtrue.s L_0015 L_000a: pop L_000b: ldsfld !0[] Gallio.Collections.EmptyArray`1<class Gallio.Model.Execution.ITestCommand>::Instance L_0010: castclass [mscorlib]System.Collections.Generic.IList`1<class Gallio.Model.Execution.ITestCommand> L_0015: stloc.0 L_0016: br.s L_0018 L_0018: ldloc.0 L_0019: ret }
The Optimized IL
When generating optimized code, the C# compiler realizes that the castclass is unnecessary. So it drops it. It also drops the unnecessary load/stores to a local variable and we end up with the following code.
.method public hidebysig specialname newslot virtual final instance class [mscorlib]System.Collections.Generic.IList`1<class Gallio.Model.Execution.ITestCommand> get_Children() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: ldfld class [mscorlib]System.Collections.Generic.List`1<class Gallio.Model.Execution.ITestCommand> Gallio.Model.Execution.ManagedTestCommand::children L_0006: dup L_0007: brtrue.s L_000f L_0009: pop L_000a: ldsfld !0[] Gallio.Collections.EmptyArray`1<class Gallio.Model.Execution.ITestCommand>::Instance L_000f: ret }
PEVerify Has a Conniption
Without the castclass in the optimized code, the IL is now missing a crucial piece of information: what is the type of the value of the top of the stack when we reach the ret instruction?
In one case, we may have duplicated the value produced by the ldfld instruction so the top has type List<ITestCommand>.
In the other case, we have discarded the original value and instead loaded an array from somewhere else using ldsfld so the top has type ITestCommand[].
Uh oh!
[IL]: Error: [C:\Source\MbUnit\v3\src\Gallio\Gallio\bin\Gallio.dll : Gallio.Model.Execution.ManagedTestCommand::get_Children][offset 0x0000000F][found ref 'System.Collections.IEnumerable'][expected ref 'System.Collections.Generic.IList`1[Gallio.Model.Execution.ITestCommand]'] Unexpected type on the stack.
Without the castclass PEVerify has to do some rather more complicated work to figure out whether the types are compatible. Apparently, it fails. But it does get far enough to realize that one of the values can be converted to IEnumerable.
Is This a Problem?
I don't know. Apparently we have been running this code in Gallio release builds for many months now and not seen any InvalidProgramException errors. So even though PEVerify is confused, the CLR at least does not choke on the code.
In any case, it's definitely not ILMerge's fault. (Sorry Mike!)
No comments:
Post a Comment