I created simple program, which dynamically generates GenericEmitExample1.dll
assembly. Such assembly defines following type:
public class Sample
{
public static string test()
{
int num = default(int);
return num.ToString();
}
}
Here is source code of such program:
using System;
using System.Reflection;
using System.Reflection.Emit;
public class Example
{
public static void Main()
{
AppDomain myDomain = AppDomain.CurrentDomain;
AssemblyName myAsmName = new AssemblyName("GenericEmitExample1");
AssemblyBuilder myAssembly = myDomain.DefineDynamicAssembly(myAsmName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder myModule =
myAssembly.DefineDynamicModule(myAsmName.Name,
myAsmName.Name + ".dll");
TypeBuilder myType = myModule.DefineType("Sample", TypeAttributes.Public);
var test_method = myType.DefineMethod("test", MethodAttributes.Public | MethodAttributes.Static, typeof(String), Type.EmptyTypes);
var gen = test_method.GetILGenerator();
var local = gen.DeclareLocal(typeof(int));
gen.Emit(OpCodes.Ldloca, local);
gen.Emit(OpCodes.Constrained, typeof(int));
gen.Emit(OpCodes.Callvirt, typeof(int).GetMethod(nameof(int.ToString), Type.EmptyTypes));
gen.Emit(OpCodes.Ret);
myType.CreateType();
myAssembly.Save(myAsmName.Name + ".dll");
}
}
There is builtin tool, named PEVerify
(https://docs.microsoft.com/en-us/dotnet/framework/tools/peverify-exe-peverify-tool). It helps to determine whether their MSIL code and associated metadata meet type safety requirements. I decided to test it, after its calling on generated assembly it shows following error message:
[IL]: Error: [GenericEmitExample1.dll : Sample::test][offset 0x00000008] Callvirt on a value type method.
1 Error(s) Verifying GenericEmitExample1.dll
Such report surprised me. Here is IL
code of generated type:
.class public auto ansi Sample
extends [mscorlib]System.Object
{
// Methods
.method public static
string test () cil managed
{
// Method begins at RVA 0x2050
// Code size 14 (0xe)
.maxstack 1
.locals init (
[0] int32
)
IL_0000: ldloca.s 0
IL_0002: constrained. [mscorlib]System.Int32
IL_0008: callvirt instance string [mscorlib]System.Int32::ToString()
IL_000d: ret
} // end of method Sample::test
.method public specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x206c
// Code size 7 (0x7)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Sample::.ctor
} // end of class Sample
I don't see any forbiden tricks/invalid IL code. callvirt
was used with constrained
prefix. Documentation (https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.constrained?view=netframework-4.8) valids this trick. Here is quote III.2.1 constrained. – (prefix) invoke a member on a value of a variable type
:
The constrained opcode allows IL compilers to make a call to a virtual function in a uniform way independent of whether ptr is a value type or a reference type. Although it is intended for the case where thisType is a generic type variable, the constrained prefix also works for nongeneric types and can reduce the complexity of generating virtual calls in languages that hide the distinction between value types and reference types.
So, what's the problem with PEVerify? Is it bug?
From Section III.2.1 of ECMA-335 (which talks about the constrained
prefix)
Verifiability:
The ptr argument will be a managed pointer (&) to
thisType
. In addition all the normal verification rules of thecallvirt
instruction apply after the ptr transformation as described above. This is equivalent to requiring that a boxedthisType
must be a subclass of the class whichmethod
belongs to.
I think you're falling foul of "This is equivalent to requiring that a boxed thisType
must be a subclass of the class which method
belongs to".
method
in your case is Int32::ToString()
, not Object::ToString()
, and so belongs to int
. However a boxed int
is not a subclass of int
.
In order to use a constrained virtual call here, you would have to call Object::ToString()
, not Int32::ToString()
.
I've verified this by changing the callvirt
instruction to:
gen.Emit(OpCodes.Callvirt, typeof(object).GetMethod(nameof(object.ToString), Type.EmptyTypes));
This verifies.
In addition:
I.12.1.6.2.4 Calling methods
Static methods on value types are handled no differently from static methods on an ordinary class: use a call instruction with a metadata token specifying the value type as the class of the method. Non-static methods (i.e., instance and virtual methods) are supported on value types, but they are given special treatment. A non-static method on a reference type (rather than a value type) expects a this pointer that is an instance of that class. This makes sense for reference types, since they have identity and the this pointer represents that identity. Value types, however, have identity only when boxed. To address this issue, the this pointer on a non-static method of a value type is a byref parameter of the value type rather than an ordinary by-value parameter.
A non-static method on a value type can be called in the following ways:
- For unboxed instances of a value type, the exact type is known statically. The
call
instruction can be used to invoke the function, passing as the first parameter (the this pointer) the address of the instance. The metadata token used with the call instruction shall specify the value type itself as the class of the method.- Given a boxed instance of a value type, there are three cases to consider:
- Instance or virtual methods introduced on the value type itself: unbox the instance and call the method directly using the value type as the class of the method.
- Virtual methods inherited from a base class: use the
callvirt
instruction and specify the method on theSystem.Object
,System.ValueType
orSystem.Enum
class as appropriate.- Virtual methods on interfaces implemented by the value type: use the
callvirt
instruction and specify the method on the interface type.
You're calling a method directly on the value type (and not on a box of it), so you should use call
.
User contributions licensed under CC BY-SA 3.0