Changing callvirt to call in IL

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.

c#
.net
il
peverify
asked on Stack Overflow Aug 13, 2015 by atlaste • edited Aug 13, 2015 by atlaste

1 Answer

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 :)

answered on Stack Overflow Aug 13, 2015 by Jon Skeet • edited Jun 20, 2020 by Community

User contributions licensed under CC BY-SA 3.0