This project is read-only.
1
Vote

Problem with short braches (Br.S and so on)

description

Hi,

I posted this issue on the discussions, but this was not the right place for it (the answer showed me this). My mistake, sorry.

So here we go again, but a little bit more detail.

The thing I want to do is to parse methods on IL level. It is done like in the samples, but I have no pdb or something like this available. The code looks like this and works fine until it hits a specific IL combination.
var testIlGenerator = new ILGenerator(host, method);
   foreach (var operation in method.Body.Operations) {
     switch (operation.OperationCode) {
         case OperationCode.Br_S:
              testIlGenerator.Emit(operation.OperationCode, operation.Value);
         break;
         default:
              testIlGenerator.Emit(operation.OperationCode, operation.Value);
         break;
      }
   }
var newBody = new ILGeneratorMethodBody(testIlGenerator, true, 8, method, Enumerable<ILocalDefinition>.Empty, Enumerable<ITypeDefinition>.Empty);
method.Body = newBody;
The IL combination that breaks the CCI Metadata framework is something like this:
[...]
4.     br.s   5 -> ldloc.0
5.     ldloc.0
[...]
This branch does not really do anything, but it is valid code. When I parse it via the code above, I get an exception when the operation is emitted to the generator. The reason seems to be that operation.value is an object of type "uint" and

public void Emit(OperationCode opcode, object value)

in IlGenerator.cs does not have any case for it and finally reaches the line
[...]
throw new InvalidOperationException();
[...]
I thought "OK, test it by changing the uint to a byte" because br.s only needs a byte. But when I do that, the PeWriter throws an exception in

private MemoryStream SerializeMethodBodyIL(IMethodBody methodBody) {

in this case:
          case OperationCode.Beq_S:
          case OperationCode.Bge_S:
          case OperationCode.Bge_Un_S:
          case OperationCode.Bgt_S:
          case OperationCode.Bgt_Un_S:
          case OperationCode.Ble_S:
          case OperationCode.Ble_Un_S:
          case OperationCode.Blt_S:
          case OperationCode.Blt_Un_S:
          case OperationCode.Bne_Un_S:
          case OperationCode.Br_S:
          case OperationCode.Brfalse_S:
          case OperationCode.Brtrue_S:
          case OperationCode.Leave_S:
            //^ assume operation.Value is uint;
            writer.WriteSbyte((sbyte)((uint)operation.Value-mbody.Position-1)); break;
The exception tells me this:

"An unhandled exception of type 'System.InvalidCastException' occurred in Microsoft.Cci.PeWriter.dll"

It seems the framework has issues with short branches (when a uint is used as argument).





My fix that I tried in the discussion thread seems to do the trick. So here is the fix again (fixed line(s) are the ones with the comment "fixed by me"):

In the function

public void Emit(OperationCode opcode, object value)

in IlGenerator.cs i added this case:
[...]
      switch (System.Convert.GetTypeCode(value)) {
        case TypeCode.Byte: this.Emit(opcode, (byte)value); break;
        case TypeCode.Double: this.Emit(opcode, (double)value); break;
        case TypeCode.Single: this.Emit(opcode, (float)value); break;
        case TypeCode.Int32: this.Emit(opcode, (int)value); break;
        case TypeCode.Int64: this.Emit(opcode, (long)value); break;
        case TypeCode.SByte: this.Emit(opcode, (sbyte)value); break;
        case TypeCode.Int16: this.Emit(opcode, (short)value); break;
        case TypeCode.String: this.Emit(opcode, (string)value); break;
        case TypeCode.Empty: this.Emit(opcode); break;


        // TODO
        // fixed by me
        case TypeCode.UInt32: this.Emit(opcode, (uint)value); break;


        default:
[...]
In

private MemoryStream SerializeMethodBodyIL(IMethodBody methodBody)

in PeWriter.cs I figured out, that the exception occurs if the value is a "long" object. I do not know why the cast does not work when the object is of type "long", but I fixed it quickly with this test code:
[...]
          case OperationCode.Beq_S:
          case OperationCode.Bge_S:
          case OperationCode.Bge_Un_S:
          case OperationCode.Bgt_S:
          case OperationCode.Bgt_Un_S:
          case OperationCode.Ble_S:
          case OperationCode.Ble_Un_S:
          case OperationCode.Blt_S:
          case OperationCode.Blt_Un_S:
          case OperationCode.Bne_Un_S:
          case OperationCode.Br_S:
          case OperationCode.Brfalse_S:
          case OperationCode.Brtrue_S:
          case OperationCode.Leave_S:
            //^ assume operation.Value is uint;


            // TODO
            // fixed by me
              if (operation.Value is long) {
                  var temp = Convert.ToUInt32(operation.Value);
                  writer.WriteSbyte((sbyte)((uint)temp - mbody.Position - 1));
                  break;
              }
              else {
                  writer.WriteSbyte((sbyte)((uint)operation.Value - mbody.Position - 1));
                  break;
              }
[...]
and now it gets translated by CCI and with Simple Assembly Explorer everything looks fine.

But still I have not enough inside in the code of the framework. So it would be nice if you could check this fix (and add it to the current version if it works).

comments