Reputation: 7587
The following code yields an exception that says that it may destabalize the runtime.
var method = new DynamicMethod("CallObjectToString", typeof(string),new[]{ typeof(object)});
var ilgen =method.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Call, typeof(object).GetMethod("ToString"));
ilgen.Emit(OpCodes.Ret);
var @delegate = method.CreateDelegate(typeof(Func<object, string>)) as Func<object,string>;
@delegate(new object());
Changing the Opcodes.Call
to Opcodes.CallVirt fixes the problem.
This is all well and good, but I can use typebuilder create a dynamic type that has a static method built using (MethodBuilder) that has exactly the same IL, then use CreateDelegate, and it will not throw this exception. In fact using methodbuilder you can make something does a call on a completely different object's virtual methods, not even one that that the type inherits from.
What's even more puzzling that if I do something like new object().ToString()
it also emits a call
instruction to the IL, rather than a callvirt
.
If it's illegal to call a virtual method using a call
except in the context of calling the base method in an instance override, then why does the CLR allow for such a method to be made staticly on a different type? Or Allow call
instructions to be emitted against known not null entities?
Or is this only an issue for code generated through DynamicMethod
?
Edit: I'm not asking from a performance standpoint as I really don't care about it. The below code while slightly more involved create a delegate that performs the destabilizing action without destabilizing the runtime. This is the entirety of the question. Why is one legal, and the other illegal?
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("SomeAssembly"), AssemblyBuilderAccess.Run) ;
var moduleBuilder = assemblyBuilder.DefineDynamicModule("SomeModule");
var typeBuilder = moduleBuilder.DefineType("SomeType");
var methodBuilder = typeBuilder.DefineMethod("SomeStaticMethod",MethodAttributes.Public | MethodAttributes.Static,typeof(string),new[]{typeof(object)});
var ilgen = methodBuilder.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Call, typeof(object).GetMethod("ToString"));
ilgen.Emit(OpCodes.Ret);
var newType = typeBuilder.CreateType();
var @delegate = Delegate.CreateDelegate(typeof(Func<object, string>),newType.GetMethod("SomeStaticMethod")) as Func<object,string>;
@delegate(new object());
Upvotes: 1
Views: 130
Reputation: 4028
It is trying to protect you from doing something wrong. A call here is wrong because the concrete type of the object cannot be known. A compiler that legally knows that the object cannot be anything other than what it sees (for example it sees a sealed class, or it just instantiated the object) can get away with because it knows it is safe, but in your case all you see is "object" passed as an argument so this transformation is not safe. In the case of "new object().ToString()" then the type of the object is absolutely concretely known to be object, so there is no need for a virtual call, however in your case the object comes in as an argument so you have absolutely no way of knowing what its concrete type is. You should not fret too much though. The JITer might know even better than you do when it comes to JIT this code, and might make the callvirt into a direct call anyway, but then again it could do the reverse and force the call into an indirect call, so there is really very little to worry about.
Upvotes: 1