evitar DynamicMethod VerificationException - operación podría desestabilizar el tiempo de ejecución
-
12-12-2019 - |
Pregunta
Estoy usando IL generación para crear un simple deserializer método que lleva cuerdas de un Lucene documento y establece las propiedades o campos de una referencia de tipo objeto (POCO).
Cada vez que intento ejecutar el método generado, voy a recibir un VerificationException de error.Hay otras preguntas acerca de este error, algunos de los cuales tienen que ver con DynamicMethods, pero puedo decir que el problema que estoy teniendo es diferente.
operación-podría-destablize-el-tiempo de ejecución-y-dynamicmethod-con-valor-de los tipos
msil-operación-que podría desestabilizar el tiempo de ejecución de excepción
Aunque el método será más complicado en el futuro, tengo que hacer simplemente de la cadena de asignación de ahora.El método que yo estoy tratando de crear de forma dinámica se verá exactamente como esta:
public static PocoObject ExampleMethod(Document document)
{
var poco = new PocoObject();
poco.ID = document.Get("ID");
poco.DisplayText = document.Get("DisplayText");
poco.PropId = document.Get("PropId");
return poco;
}
Donde PocoObject se parece a esto:
class PocoObject
{
public string ID;
public string DisplayText;
public string PropId { get; set; }
}
He tratado de reproducir la IL generado a partir de la compilación de código de tiempo EXACTAMENTE (incluso la innecesaria bits) y se parece a esto:
.method public instance object 'Deserializebe6d500b-d35f-4f7a-a9b3-88f6bca5fb93'(class [Lucene.Net]Lucene.Net.Documents.Document A_1) cil managed
{
// Code size 65 (0x41)
.maxstack 4
IL_0000: nop
IL_0001: newobj instance void [LukeMapperTest]LukeMapperTest.PocoObject::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldarg.0
IL_0009: ldstr "ID"
IL_000e: callvirt instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
IL_0013: stfld string [LukeMapperTest]LukeMapperTest.PocoObject::ID
IL_0018: ldloc.0
IL_0019: ldarg.0
IL_001a: ldstr "DisplayText"
IL_001f: callvirt instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
IL_0024: stfld string [LukeMapperTest]LukeMapperTest.PocoObject::DisplayText
IL_0029: ldloc.0
IL_002a: ldarg.0
IL_002b: ldstr "PropId"
IL_0030: callvirt instance string [Lucene.Net]Lucene.Net.Documents.Document::Get(string)
IL_0035: callvirt instance void [LukeMapperTest]LukeMapperTest.PocoObject::set_PropId(string)
IL_003a: nop
IL_003b: ldloc.0
IL_003c: stloc.1
IL_003d: br.s IL_003f
IL_003f: ldloc.1
IL_0040: ret
} // end of method Test::'Deserializebe6d500b-d35f-4f7a-a9b3-88f6bca5fb93'
He conseguido ahorrar el DynamicMethod a una asamblea en el disco, inspeccionado, y esto es exactamente lo que trae.La línea es el mismo que el tiempo de compilación método IL.
Sin embargo, cuando se ejecuta el método dinámico, el error anterior se produce.¿Alguien tiene alguna idea de cómo podría solucionar este problema?
Nota:Tengo el código fuente en GitHub si alguien quiere echar un vistazo más de cerca.La IL Generación de código es en LukeMapper.cs:GetDumbDeserializer() (línea 133)
Ayuda es muy apreciada.Gracias!
EDITAR:Así que he simplificado la IL generación de código y es, esencialmente, las siguientes:
private static Func<Document, object> GetDumbDeserializer(Type type)
{
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(Document) }, true);
var il = dm.GetILGenerator();
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
il.Emit(OpCodes.Nop);
il.DeclareLocal(type);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_0);
Label returnLabel = il.DefineLabel();
//stack is [target]
var getFieldValue = typeof(Document).GetMethod("Get", BindingFlags.Instance | BindingFlags.Public);
foreach (var setter in settableProperties)
{
il.Emit(OpCodes.Ldloc_0);// [target]
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, setter.Name);
il.Emit(OpCodes.Callvirt, getFieldValue);
il.Emit(OpCodes.Stfld, setter.Field);
}
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Stloc_1); // stack is empty
il.Emit(OpCodes.Br_S, returnLabel);
il.MarkLabel(returnLabel);
il.Emit(OpCodes.Ldloc_1); // stack is [rval]
il.Emit(OpCodes.Ret);
return (Func<Document, object>)dm.CreateDelegate(typeof(Func<Document, object>));
}
He añadido en el il.DeclareLocal(type)
(donde tipo es PocoObject) por kvbs del comentario, pero no estoy seguro de si lo estoy poniendo en el lugar correcto (o si lo que importa).
Solución
Es el galimatías al final, y el uso de Stfld
para llamar a una propiedad;No tengo idea de donde ese fin de cosas, pero yo no creo que vino de ExampleMethod
- Creo que podría haber sido de una generación anterior de los suyos.En particular, no se ha definido un segundo local, por lo que Ldloc_1
no tiene ningún sentido;pero no hay absolutamente ninguna necesidad de etiquetas / sucursales aquí.Aquí es lo que tengo, que funciona (nota yo no sabía lo que su settableProperties
fueron, así que lo hice solo uso FieldInfo
/ PropertyInfo
:
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(Document) }, true);
var il = dm.GetILGenerator();
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
il.DeclareLocal(type);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_0);
var getFieldValue = typeof(Document).GetMethod("Get", BindingFlags.Instance | BindingFlags.Public);
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var field in fields)
{
il.Emit(OpCodes.Ldloc_0);// [target]
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, field.Name);
il.Emit(OpCodes.Callvirt, getFieldValue);
il.Emit(OpCodes.Stfld, field);
}
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
var setter = prop.GetSetMethod();
if (setter == null) continue;
il.Emit(OpCodes.Ldloc_0);// [target]
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, prop.Name);
il.Emit(OpCodes.Callvirt, getFieldValue);
il.EmitCall(OpCodes.Callvirt, setter, null);
}
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);
return (Func<Document, object>)dm.CreateDelegate(typeof(Func<Document, object>));
Y para efectos de comparación, aquí es lo que me pasa cuando se mira en ExampleMethod
en el reflector (en una versión de lanzamiento, etc):
.method public hidebysig static class PocoObject ExampleMethod(class Document document) cil managed
{
.maxstack 3
.locals init (
[0] class PocoObject poco)
L_0000: newobj instance void PocoObject::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldarg.0
L_0008: ldstr "ID"
L_000d: callvirt instance string Document::Get(string)
L_0012: stfld string PocoObject::ID
L_0017: ldloc.0
L_0018: ldarg.0
L_0019: ldstr "DisplayText"
L_001e: callvirt instance string Document::Get(string)
L_0023: stfld string PocoObject::DisplayText
L_0028: ldloc.0
L_0029: ldarg.0
L_002a: ldstr "PropId"
L_002f: callvirt instance string Document::Get(string)
L_0034: callvirt instance void PocoObject::set_PropId(string)
L_0039: ldloc.0
L_003a: ret
}
Cosas a tener en cuenta:
- no hay etiquetas / de ramificación (no tengo idea de dónde vino, pero que no es de lo que has publicado)
- se define un local
- no
nop