Question

Fair warning, this may be a tad esoteric and tricky.

Given a MemberRef (more explanation below) extracted from a CIL stream, how do you figure out what field, if any, it points to (and get a FieldInfo for it)?

Here's what I've figured out so far

According to the ECMA 335 standard, a MemberRef is a metadata token that is basically a lookup in a table that could point to either a field metadata token or a method metadata token. Any metadata token beginning 0x0A is a MemberRef.

enter image description here

I hadn't encountered one of these before, but they don't seem that uncommon. I was able to get one generated by using the following anonymous type in a method:

new
{
    A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
    B = (DateTime?)null
}

When I grab the method body via reflection (get the PropertyInfo, get the GetMethod, get the MethodBody, then finally get the IL) A's get method is:

[2, 123, 79, 0, 0, 10, 42]

Which converts to:

ldarg.0
ldfld 0x0A00004F
ret

If I reflect in and get the backing field (relying on name similarity to choose <A>i__Field, nothing algorithmic) I see that the MetadataToken is 0x04000056.

Note that the tokens generated may vary between compilations.

A token starting 0x04 is a field: enter image description here

Most of the time (for all non-anonymous objects in my limited testing, in fact) the IL contains a field metadata token. This is easy to turn into a FieldInfo via Module.ResolveField(int), figuring out what to do with a MemberRef is tripping me up.

Cycling through the other ResolveXXX methods on Module, the only one that can do anything with a MemberRef is ResolveSignature. When run on the above MemberRef, it returns an array of [6, 19, 0]. I don't really know what to make of that.

The code I'm working on is unfinished, but public. The error can be seen by running this test, causing an exception to be thrown when a field lookup fails on this line. Note that the test itself is unfinished, it's not expected to succeed yet but it shouldn't die there either.

Anybody have any idea what to make of that signature, or some other way to get to a field's metadata token (and thus its FieldInfo) from the MemberRef?

Here's a LINQPad program script that reproduces the problem. It's pretty big, there's a lot of boilerplate.

void Main()
{
    Init();
    
    var obj = 
        new
        {
            A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
            B = (DateTime?)null
        };
    
    var usage = PropertyFieldUsage(obj.GetType());
    usage.Dump();
}

private static Dictionary<int, System.Reflection.Emit.OpCode> OneByteOps;
private static Dictionary<int, System.Reflection.Emit.OpCode> TwoByteOps;

public static Dictionary<PropertyInfo, List<FieldInfo>> PropertyFieldUsage(Type t)
{
  var ret = new Dictionary<PropertyInfo, List<FieldInfo>>();

  var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetMethod != null);

  var module = t.Module;

  foreach (var prop in props)
  {
      var getMtd = prop.GetMethod;
      var mtdBody = getMtd.GetMethodBody();
      var il = mtdBody.GetILAsByteArray();

      var fieldHandles = _GetFieldHandles(il);

      var fieldInfos = 
          fieldHandles
              .Select(
                  f => module.ResolveField(f)
              ).ToList();

      ret[prop] = fieldInfos;
  }

  return ret;
}

// Define other methods and classes here
private static List<int> _GetFieldHandles(byte[] cil)
{
  var ret = new List<int>();

  int i = 0;
  while (i < cil.Length)
  {
      int? fieldHandle;
      System.Reflection.Emit.OpCode ignored;
      var startsAt = i;
      i += _ReadOp(cil, i, out fieldHandle, out ignored);

      if (fieldHandle.HasValue)
      {
          ret.Add(fieldHandle.Value);
      }
  }

  return ret;
}

private static int _ReadOp(byte[] cil, int ix, out int? fieldHandle, out System.Reflection.Emit.OpCode opcode)
{
  const byte ContinueOpcode = 0xFE;

  int advance = 0;

  byte first = cil[ix];

  if (first == ContinueOpcode)
  {
      var next = cil[ix + 1];

      opcode = TwoByteOps[next];
      advance += 2;
  }
  else
  {
      opcode = OneByteOps[first];
      advance++;
  }

  fieldHandle = _ReadFieldOperands(opcode, cil, ix, ix + advance, ref advance);

  return advance;
}

private static int? _ReadFieldOperands(System.Reflection.Emit.OpCode op, byte[] cil, int instrStart, int operandStart, ref int advance)
{
  Func<int, int> readInt = (at) => cil[at] | (cil[at + 1] << 8) | (cil[at + 2] << 16) | (cil[at + 3] << 24);

  switch (op.OperandType)
  {
      case System.Reflection.Emit.OperandType.InlineBrTarget:
          advance += 4;
          return null;

      case System.Reflection.Emit.OperandType.InlineSwitch:
          advance += 4;
          var len = readInt(operandStart);
          var offset1 = instrStart + len * 4;
          for (var i = 0; i < len; i++)
          {
              advance += 4;
          }
          return null;

      case System.Reflection.Emit.OperandType.ShortInlineBrTarget:
          advance += 1;
          return null;

      case System.Reflection.Emit.OperandType.InlineField:
          advance += 4;
          var field = readInt(operandStart);
          return field;

      case System.Reflection.Emit.OperandType.InlineTok:
      case System.Reflection.Emit.OperandType.InlineType:
      case System.Reflection.Emit.OperandType.InlineMethod:
          advance += 4;
          return null;

      case System.Reflection.Emit.OperandType.InlineI:
          advance += 4;
          return null;

      case System.Reflection.Emit.OperandType.InlineI8:
          advance += 8;
          return null;

      case System.Reflection.Emit.OperandType.InlineNone:
          return null;

      case System.Reflection.Emit.OperandType.InlineR:
          advance += 8;
          return null;

      case System.Reflection.Emit.OperandType.InlineSig:
          advance += 4;
          return null;

      case System.Reflection.Emit.OperandType.InlineString:
          advance += 4;
          return null;

      case System.Reflection.Emit.OperandType.InlineVar:
          advance += 2;
          return null;

      case System.Reflection.Emit.OperandType.ShortInlineI:
          advance += 1;
          return null;

      case System.Reflection.Emit.OperandType.ShortInlineR:
          advance += 4;
          return null;

      case System.Reflection.Emit.OperandType.ShortInlineVar:
          advance += 1;
          return null;

      default: throw new Exception("Unexpected operand type [" + op.OperandType + "]");
  }
}

static void Init()
{
  var oneByte = new List<System.Reflection.Emit.OpCode>();
  var twoByte = new List<System.Reflection.Emit.OpCode>();

  foreach (var field in typeof(System.Reflection.Emit.OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static))
  {
      var op = (System.Reflection.Emit.OpCode)field.GetValue(null);

      if (op.Size == 1)
      {
          oneByte.Add(op);
          continue;
      }

      if (op.Size == 2)
      {
          twoByte.Add(op);
          continue;
      }

      throw new Exception("Unexpected op size for " + op);
  }

  OneByteOps = oneByte.ToDictionary(d => (int)d.Value, d => d);
  TwoByteOps = twoByte.ToDictionary(d => (int)(d.Value & 0xFF), d => d);
}
Was it helpful?

Solution

The trick here is that it is a generic type (the second parameter to ResolveField), and we know that the getter is not a generic method (the final parameter to ResolveField), so you need to use ResolveField like so:

var obj = new
{
    A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
    B = (DateTime?)null
};

Parse(obj, "A");
Parse(obj, "B");

static void Parse(object obj, string property)
{
    var blob = obj.GetType().GetProperty(property).GetGetMethod()
       .GetMethodBody().GetILAsByteArray();
    // hard-code that we know the token is at offset 2
    int token = BitConverter.ToInt32(blob, 2);

    var field = obj.GetType().Module.ResolveField(token,
        obj.GetType().GetGenericArguments(), null);
    Console.WriteLine(field.Name);
    Console.WriteLine(field.MetadataToken);
}

In the more general case, where you don't know as much about the type (i.e. that it might be a non-generic type) and the method (although strictly speaking, property accessors are never generic in their own right, but this shows broad usage):

static MemberInfo ResolveMember(this MethodInfo method, int metadataToken)
{
    Type type = method.DeclaringType;
    Type[] typeArgs = null, methodArgs = null;

    if (type.IsGenericType || type.IsGenericTypeDefinition)
        typeArgs = type.GetGenericArguments();
    if (method.IsGenericMethod || method.IsGenericMethodDefinition)
        methodArgs = method.GetGenericArguments();

    return type.Module.ResolveMember(metadataToken, typeArgs, methodArgs);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top