3

During some experiments with IL, I attempted to change callvirt calls in an assembly to call methods. Basically what happens is that I have an inheritance chain with member functions that I am calling.

Basically the call is similar to this:

((MyDerivedClass)myBaseObject).SomeCall();

or in IL

castclass MyDerivedClass   // ** 1
call SomeCall()            // ** 2

The base class defines SomeCall as abstract method, the derived class implements it. The derived class is sealed.

I know that callvirt is basically the equivalent of check if the object is null, if it's not call the method using the vtable and if it is, throw an exception. In my case I know that it's never null and I know that's the implementation I want to call. I understand why you would normally need a callvirt in such a case.

That said, because I know that the object is never null, and always is an instance of the derived type, I would think it's not a problem:

  • When you consider that data and types are separated, I'd actually figure that (**1) could be removed (the data of the object will be the same) and
  • That (**2) can be a simple call, since we know exactly what member to call. No vtable lookup is necessary.

It also seemed to me like a quite reasonable thing the compiler can also deduce in some cases. For those interested, yes, there is a speed penalty for callvirt, although it's pretty small.

However. PEVerify tells me it's wrong. And as a good boy, I always take note of what PEVerify is telling me. So what am I missing here? Why does changing this call lead to an incorrect assembly?


Apparently creating a minimum test case isn't so simple... so far I don't have a lot of luck with it.

As for the issue itself, I can simply reproduce it in a larger program:

[IL]: Error: [C:\tmp\emit\test.dll : NubiloSoft.Test::Value][offset 0x00000007] The 'this' parameter to the call must be the calling method's 'this' parameter.

IL code of Value:

L_0000: ldarg.0 
L_0001: ldfld class NubiloSoft.Test SomeField
L_0006: ldarg.1 
L_0007: call instance bool NubiloSoft.Test::Contains(int32)

The type of the field is NubiloSoft.Test.

As for Contains, it's abstract in a base class, and in the derived class it's overridden. Just as you would expect. When I remove the 'abstract base method' + 'override', PEVerify likes it all again.

In an attempt to reproduce the issue I did this, so far without luck to reproduce it in a minimal test case:

public abstract class FooBase
{
    public abstract void MyMethod();
}

// sealed doesn't seem to do anything...
public class FooDerived : FooBase 
{
    public override void MyMethod()
    {
        Console.WriteLine("Hello world!");
    }
}

public class FooGenerator
{
    static void Main(string[] args)
    {
        Type t = CreateClass();

        object o = Activator.CreateInstance(t, new[] { new FooDerived() });
        var meth = t.GetMethod("Caller");
        meth.Invoke(o, new object[0]);

        Console.ReadLine();
    }

    public static Type CreateClass()
    {
        // Create assembly
        var assemblyName = new AssemblyName("testemit");
        var assemblyBuilder =
            AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
            AssemblyBuilderAccess.RunAndSave, @"c:\tmp");

        // Create module
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("testemit", "test_emit.dll", false);

        // Create type : IFoo
        var typeBuilder = moduleBuilder.DefineType("TestClass", TypeAttributes.Public, typeof(object));

        // Apparently we need a field to trigger the issue???
        var field = typeBuilder.DefineField("MyObject", typeof(FooDerived), FieldAttributes.Public);

        ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig |
            MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
            CallingConventions.HasThis, new Type[] { typeof(FooDerived) });

        // Generate the constructor IL. 
        ILGenerator gen = constructorBuilder.GetILGenerator();

        // The constructor calls the constructor of Object
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));

        // Store the field
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Stfld, field);

        // Return
        gen.Emit(OpCodes.Ret);

        // Add the 'Second' method
        var mb = typeBuilder.DefineMethod("Caller",
            MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual |
            MethodAttributes.Final | MethodAttributes.Public, CallingConventions.HasThis,
            typeof(void), Type.EmptyTypes);

        // Implement
        gen = mb.GetILGenerator();

        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldfld, field);
        gen.Emit(OpCodes.Call, typeof(FooDerived).GetMethod("MyMethod"));
        gen.Emit(OpCodes.Ret);

        Type result = typeBuilder.CreateType();

        assemblyBuilder.Save("testemit.dll");

        return result;
    }
}

When you run it and call peverify, it'll tell you the code doesn't have bugs... :-S

I'm not sure what's going on here... seems to me like it's pretty similar.

atlaste
  • 30,418
  • 3
  • 57
  • 87
  • Does peverify give a specific message? Can you provide a short but complete IL program that demonstrates the problem? – Jon Skeet Aug 13 '15 at 06:20
  • I encountered it as a part of a much larger program. Let me see if I can brew up a minimal test case. – atlaste Aug 13 '15 at 06:55
  • @JonSkeet I did my best but so far without any luck to reproduce the issue in a minimum test case scenario. What's even more strange is that the code of the minimum test case is pretty much the same IMO. – atlaste Aug 13 '15 at 07:24
  • I'm not sure why you're using C# here - why not just show the IL that can't be verified? I'd just compile a small C# program, then decompile it to IL with ildasm, modify the IL, reassemble it with ilasm, and then peverify it... – Jon Skeet Aug 13 '15 at 07:28
  • @JonSkeet I added the IL code of Value. That's everything there is in it. As for the C# code, I just had that lying around... doesn't really matter now, does it? – atlaste Aug 13 '15 at 07:33
  • Well the question would be better if it basically consisted of a short but complete piece of IL which peverify didn't like. I can try to whip that up if you like, but probably not right now... – Jon Skeet Aug 13 '15 at 07:35

1 Answers1

2

I suspect this blog post is relevant. In particular:

Some consider this a violation of privacy through inheritence. Lots of code is written under the assumption that overriding a virtual method is sufficient to guarantee custom logic within gets called. Intuitively, this makes sense, and C# lulls you into this sense of security because it always emits calls to virtual methods as callvirts.

And then:

Late in Whidbey, some folks decided this is subtly strange enough that we at least don’t want partially trusted code to be doing it. That it’s even possible is often surprising to people. We resolved the mismatch between expectations and reality through the introduction of a new verification rule.

The rule restricts the manner in which callers can make non-virtual calls to virtual methods, specifically by only permitting it if the target method is being called on the caller’s ‘this’ pointer. This effectively allows an object to call up (or down, although that would be odd) its own type hierarchy.

In other words, assuming this change is what you're talking about (it sounds like it) the rule is there to prevent IL from violating normal expectations of how virtual methods are called.

You might want to try making the SomeCall method sealed in MyDerivedClass... at that point it's not virtual any more in the sense that a call to SomeCall on a reference of type MyDerivedClass will always call the same method... whether that's sufficiently non-virtual for peverify is a different matter though :)

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Interestingly (or maybe not for people more informed than me) - `var h = new concrete(); h.SomeCall()` generates a `callvirt` instruction, even when `concrete` is a sealed class, and does not inherit from anything (other than object, of course). I would have expected this to use `call`. What's more interesting is that `new concrete().SomeCall()` *does* use the `call` instruction, though they should be equivalent – Rob Aug 13 '15 at 06:29
  • 1
    @Rob: I suspect this is more for the sake of null protection - even in the face of "obviously non-null" targets - than anything else. When you think about how complex the compiler is, I don't think I'd want to add complexity by suggesting it should emit different IL based on whether or not it "knows" a reference to be null. Also, if Concrete is in a different assembly, it may turn out that it's *not* sealed by the time the code runs... The JIT is in a better position to omit the nullity check here and vtable lookup, I suspect. – Jon Skeet Aug 13 '15 at 06:35
  • @Rob Is that also the case in the new Roslyn compiler? Either way, RyuJIT uses SSA so in that case it should be equivalent on an SSA level. In the older .NET 4.0 compiler that's not really the case iirc. As you can read in my question, the calls to `callvirt` are 'by default' added for implicit null checks. It can also be removed later on with dead code elimination. – atlaste Aug 13 '15 at 07:03