empêcher DynamicMethod VerificationException - l'opération pourrait déstabiliser le runtime

StackOverflow https://stackoverflow.com//questions/11680490

  •  12-12-2019
  •  | 
  •  

Question

J'utilise la génération IL pour créer une méthode de désérialisation simple qui extrait les chaînes d'un document Lucene et définit les propriétés ou les champs d'un objet de type référence (POCO).

Chaque fois que j'essaie d'exécuter la méthode générée, je reçois une erreur VerificationException.Il y a d'autres questions concernant cette erreur, dont quelques-unes concernent DynamicMethods, mais d'après ce que je peux dire, le problème que je rencontre est différent.

l'opération-pourrait-déstabiliser-le-runtime-et-la-méthode-dynamique-avec-des-types-de-valeur

msil-opération-pourrait-déstabiliser-l'exception-d'exécution

Bien que la méthode devienne plus compliquée à l'avenir, je la fais juste en train d'assigner des chaînes pour le moment.La méthode que j'essaie de créer dynamiquement ressemblera exactement à ceci :

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;
}

Où PocoObject ressemble à ceci :

class PocoObject
{
    public string ID;
    public string DisplayText;

    public string PropId { get; set; }

}

J'ai essayé de répliquer EXACTEMENT l'IL généré à partir du code de compilation (même les bits inutiles) et cela ressemble à ceci :

.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'

J'ai réussi à enregistrer le DynamicMethod dans un assembly sur le disque, je l'ai inspecté et c'est exactement ce qu'il affiche.Ligne par ligne, c'est la même chose que la méthode de compilation IL.

Néanmoins, lors de l'exécution de la méthode dynamique, l'erreur ci-dessus est générée.Quelqu'un a-t-il une idée de comment résoudre ce problème ?

Note:J'ai le code source sur GitHub si quelqu'un souhaite le regarder de plus près.Le code de génération IL est dans LukeMapper.cs:GetDumbDeserializer() (ligne 133)

Dépôt GitHub LukeMapper

Toute aide est appréciée !Merci!


MODIFIER:J'ai donc simplifié le code de génération IL et c'est essentiellement le suivant :

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>));
}

J'ai ajouté dans le il.DeclareLocal(type) (où type est PocoObject) selon le commentaire de kvbs, mais je ne sais pas si je le mets au bon endroit (ou si c'est important).

Était-ce utile?

La solution

C'est le charabia à la fin, et en utilisant Stfld appeler une propriété;Je n'ai aucune idée d'où vient ce truc final, mais je ne pas je crois que ça vient de ExampleMethod - Je pense que cela pourrait provenir d'une de vos versions précédentes.En particulier, vous n'avez pas défini de deuxième local, donc Ldloc_1 ça n'a aucun sens;mais il n'y a absolument aucun besoin d'étiquettes/branches ici.Voici ce que j'ai, qui fonctionne (notez que je ne savais pas ce que votre settableProperties l'étaient, alors je l'ai fait simplement en utilisant 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>));

Et à titre de comparaison, voici ce que j'obtiens en regardant ExampleMethod dans le réflecteur (dans une version release, 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 
}

Choses à noter :

  • pas d'étiquettes/branchement (je n'ai aucune idée d'où cela vient, mais cela ne vient pas de ce que vous avez posté)
  • il définit un local
  • Non nop
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top