Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Digital Nirvana: Where does a callvirt of a non-existent method end up?

I call a property set-accessor on a library class which in its base class is marked as abstract. Now at runtime I force the application to run against another version of the library where the class implements only the underlying interfaces of the base class, but isn't derived from it.

Interestingly, .NET will run the code, but setting the property has no effect. What's going on behind the scenes?

Offending Code:

MyDbParameter param = new MyDbParameter();
param.ParameterName = "p";
Console.Out.WriteLine("ParameterName: " + param.ParameterName);

Library 2.0 (compiled)

public sealed class MyDbParameter : System.Data.Common.DbParameter
{
    public override string ParameterName
    {
       get { return _name; }
       set { _name = value; }
    }
    //...
}

Library 1.0 (run)

public sealed class MyDbParameter : MarshalByRefObject, IDbDataParameter, IDataParameter
{
    public string ParameterName
    {
        get { return _name; }
        set { _name = value; }
    }
    //...
}

Looking at the MSIL of the calling code I suppose the virtual call is resolved via the MetodTable of the base class:

IL_0001: newobj instance void [Library]Library.MyDbParameter::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "p"
IL_000d: callvirt instance void [System.Data]System.Data.Common.DbParameter::set_ParameterName(string)

But the base class does not exist when running the code - neither does DbParameter.set_ParameterName(). How is it possible that .NET doesn't complain about that? Which method actually gets called?


UPDATE:

As suggested by Samuel I've decompiled the class System.Data.Common.DbParameter and adopted it in both of my libraries. The behavior reproduces regardless of wherher I derive from MarshalByRefObject or comment all of it out - hereby I believe I have falsified Mason's answer.

But during the process I've discovered what happens: In reality it is the setter/getter of some other property of MyDbParameter in Library 1 what is called, e.g. Size (which is of type int!) - it depends on the property order in code. In the previous case I implemented the setters of other properties so that I ignored the supplied values, therefore I could see no effect. Now if all of them have automatic getters/setters, the output of my code is actually correct.

The question remains: Why doesn't .NET complain about the missing method at runtime?

like image 664
metalheart Avatar asked Jan 23 '13 05:01

metalheart


1 Answers

I believe your nirvana is filled with MarshalByRefObjects. They are handled uniquely by the framework, as it instruments all access to them in order to treat them as proxies for remote objects. The MBRO can't actually fulfill the offending code's request (since your v1 class doesn't support DbParameter::set_ParameterName), so it is taking the long road to nowhere. It isn't treated as a MissingMethodException since MBRO's are usually missing requested members, so the runtime is far more lenient.

However, if you tried changing your offending code to cast param as an IDbDataParameter before setting the property, I imagine it would work, since both v1 and v2 support that interface.

like image 136
Mason of Words Avatar answered Oct 17 '22 16:10

Mason of Words