2

I am using IL generation to create a simple deserializer method which takes strings out of a Lucene document and sets properties or fields of a reference-type object (POCO).

Whenever I try to run the generated method, I receive a VerificationException error. There are other questions regarding this error, a few of which have to do with DynamicMethods, but from what I can tell the issue I am having is different.

operation-could-destablize-the-runtime-and-dynamicmethod-with-value-types

msil-operation-could-destabilize-the-runtime-exception

Although the method will get more complicated in the future, I have it just doing string assignment right now. The method I am trying to create dynamically will look exactly like this:

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

Where PocoObject looks like this:

class PocoObject
{
    public string ID;
    public string DisplayText;

    public string PropId { get; set; }

}

I tried to replicate the IL generated from the compile-time code EXACTLY (even the unnecessary bits) and it looks like this:

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

I have managed to save the DynamicMethod to an assembly on disk, inspected it, and this is exactly what it brings up. Line for line it is the same as the compile-time method IL.

Nevertheless, when executing the dynamic method, the error above is thrown. Does anyone have any idea how I could fix this?

Note: I have the source code on GitHub if anyone would like to take a closer look. The IL Generation code is in LukeMapper.cs:GetDumbDeserializer() (line 133)

LukeMapper GitHub repo

All help is appreciated! Thanks!


EDIT: So I have simplified the IL generation code and it is essentially the following:

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

I added in the il.DeclareLocal(type) (where type is PocoObject) per kvbs's comment, but I'm not sure if I am putting it in the right place (or if it matters).

Community
  • 1
  • 1
Leland Richardson
  • 2,695
  • 2
  • 20
  • 27
  • 2
    What does PEVerify say about your generated code? – svick Jul 27 '12 at 02:16
  • @svick: So i had never used PEVerify before, but pointing it to the saved .dll registered one error: Method is not visible. Is that a relevant error? Also, svick: were you the downvoter? If so could you let me know what I could have done different? – Leland Richardson Jul 27 '12 at 02:30
  • @kvb: I believe so... there aren't really any locals. The strings (lucene doc field names) are rendered as constants, so the only locals are those and the returned object, which is loaded properly (i believe) – Leland Richardson Jul 27 '12 at 02:35
  • @LelandRichardson There certainly are locals in the CIL you posted: the `stloc` and `ldloc` instructions use them. – svick Jul 27 '12 at 02:36
  • Note: in regards to the PEVerify error: I don't think this is relevant as this is looking at the *saved* assembly versus the dynamic method, which is invoked with the "restructedSkipVisibility" parameter set to true... – Leland Richardson Jul 27 '12 at 02:36
  • @svik: well, yes - but that is just loading the Lucene Document (the method parameter), and the returned object, right? – Leland Richardson Jul 27 '12 at 02:38
  • @LelandRichardson - you need to call `DeclareLocal` on your `ILGenerator` for each local you use (in this case you've got two locals of type `PocoObject`). Also, the return type of your method looks wrong (`object` vs. `PocoObject`). – kvb Jul 27 '12 at 04:07
  • @kvb: thanks for your help. I initially had the return type as PocoObject vs object but I am emulating SamSaffron's code in dapper and he has it as `object` so I changed it to that... I think you were right about me missing the `il.DeclareLocal()` (see my edits) but it still didn't seem to fix it... do you have any more ideas??? Did i put the DeclareLocal in the right place? – Leland Richardson Jul 27 '12 at 04:35

1 Answers1

3

It is the gibberish at the end, and using Stfld to call a property; I have no idea where that end stuff came from, but I do not believe that came from ExampleMethod - I think that might have been from a previous build of yours. In particular, you haven't defined a second local, so Ldloc_1 makes no sense; but there is absolutely no need for any labels / branches here. Here is what I have, that works (note I didn't know what your settableProperties were, so I did it just using 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>));

And for comparison purposes, here is what I get when looking at ExampleMethod in reflector (in a release build, 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 
}

Things to note:

  • no labels / branching (I have no idea where that came from, but that is not from what you posted)
  • it defines a local
  • no nop
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thank's Marc! This worked... Also - I put the `nop`s and the branching at the end in there only later just because I was frustrated and wanted to get the IL to be exactly like what I found in ildasm. - no idea why they are there but they look to be useless (changing to release build gets rid of them...). In the end the issue i was having was with the `Ldloc.1` which was copied from sam's code which I mustn't have understood completely. Thanks again. – Leland Richardson Jul 27 '12 at 15:13
  • oh and also - i wasn't using `stfld` on properties... i just took out that part of code in my "simplification" that i posted. Cheers. – Leland Richardson Jul 27 '12 at 15:14