Generating Exception Blocks

Recently I've been doing a lot of work generating dynamic types in .NET. I duplicated Java's dynamic proxy architecture, which forced me to start learning CIL and the System.Reflection.Emit classes. I hope to share what I did in detail in a published format soon, but right now I'll share an interesting side effect that the Emitter classes can have on your generated CIL.

During the creation of my Proxy class, I relied heavily on reverse-engineering techniques to help me understand how CIL works. I'd create my intended type in C#, load the assembly into ILDasm, and translate the CIL into a bunch of Emit() calls. I had done this a lot in the project, and while it was getting tedious, I was becoming comfortable with it.

I had to make a change to the code that generates a method called InvokeMethod(). This method had an if statement near the top of the method:

if(null == wrappedInfo.InnerMethod)

The generated CIL looked like this:

IL_0017:  ldloca.s   wrappedInfo
IL_0019:  ldfld      class [mscorlib]System.Reflection.MethodInfo       
	Viktor.TestProxy.InnerMethodInfo::InnerMethod
IL_001e:  brfalse.s  IL_009b

OK, no problem. Here's what the CIL looks like when I converted them into a bunch of Emit() calls:

invokeMethodIL.Emit(OpCodes.Ldloca_S, wrappedInfo);
invokeMethodIL.Emit(OpCodes.Ldfld, 
	innerMethodInfo.GetField(INNER_METHOD));
Label IL_009b = invokeMethodIL.DefineLabel();
invokeMethodIL.Emit(OpCodes.Brfalse_S, IL_009b);

I had done this so many times before, I didn't expect this to be a problem. But, lo and behold, when I ran my dynamic type through my tests, I got the following exception:

System.NotSupportedException: Illegal one-byte branch at position: 31. Requested branch was: 143.
   at System.Reflection.Emit.ILGenerator.BakeByteArray()
   at System.Reflection.Emit.MethodBuilder.CreateMethodBodyHelper(ILGenerator il)
   at System.Reflection.Emit.TypeBuilder.CreateType()

Well, this confused the hell out of me! I checked, double-checked, and triple-checked my C# code. Everything seemed fine - I had a couple of bugs, but when I fixed them I still got the error. So I made a post to a .NET list, hoping to get some help. Fortunately, I got a helpful response. Basically, the problem is this. brfalse.s is a "short" branch instruction. Using this opcode, you can only jump forward (or back) 127 bytes of CIL instructions. However, I'm trying to make a 143-byte branch in my dynamically-generated type, so when I try to "bake" the type, BakeByteArray() reports an error.

The quick fix was to change Brfalse_S to Brfalse. That eliminated the exception and everything worked, but I was bothered by the fact that the assembly I was using to reverse-engineer the CIL generated brfalse.s! Why wouldn't it work when I dynamically created it?

Finally, I realized (through the help of Joe Hummel) to simply look at the CIL in the base assembly and dynamically-generated one, just to make sure the CIL is identical. I initially thought this wasn't necessary as my C# code has the exact same CIL instuctions emitted by Emit(). However, when I compared the CIL, I was suprised. The dynamic type's CIL looks like this:

.try
{
  IL_003f:  ldarg.0
  IL_0040:  ldfld      class [DynamicProxiesClient]Viktor.DynamicProxiesClient.frmMain 
	Viktor.DynamicProxiesClient.SomeClass58168328::invokeHandler
  IL_0045:  ldloc.s    V_5
  IL_0047:  ldloc.s    V_4
  IL_0049:  ldarg.2
  IL_004a:  ldloca.s   V_1
  IL_004c:  ldloca.s   V_0
  IL_004e:  callvirt   instance void 
	[DynamicProxies]Viktor.DynamicProxies.InvocationHandler::BeforeMethodInvocation(string,
	string, object[]&, bool&, bool&)
  IL_0053:  leave.s    IL_0067
  IL_0055:  leave      IL_0067
}  // end .try
catch [mscorlib]System.Exception 
{

Now, my code is generated the last leave.s opcode, but I was sure I didn't generate the leave opcode. Well, I wasn't doing it directly with an Emit() call. It was happening in calls to EndExceptionBlock() and BeginCatchBlock():

invokeMethodIL.BeginExceptionBlock();
invokeMethodIL.Emit(OpCodes.Ldarg_0);
invokeMethodIL.Emit(OpCodes.Ldfld, invokeHandler);
invokeMethodIL.Emit(OpCodes.Ldloc_S, interfaceName);
invokeMethodIL.Emit(OpCodes.Ldloc_S, methodName);
invokeMethodIL.Emit(OpCodes.Ldarg_2);
invokeMethodIL.Emit(OpCodes.Ldloca_S, doInvoke);
invokeMethodIL.Emit(OpCodes.Ldloca_S, raiseException);
invokeMethodIL.EmitCall(OpCodes.Callvirt, 
	typeof(Viktor.DynamicProxies.InvocationHandler).GetMethod("BeforeMethodInvocation"), null);
Label IL_005c = invokeMethodIL.DefineLabel();
invokeMethodIL.Emit(OpCodes.Leave_S, IL_005c);
//    }
//    catch(Exception beforeEx)	
invokeMethodIL.BeginCatchBlock(typeof(System.Exception));

If I took out the last call to Emit(), I only got the leave opcode. Of course, this is a bigger opcode than the one I wanted, so I still got the exception I had before. But at least I knew where the extra bits were coming from.

So when you're creating types in .NET and you're using ILDasm to help you generate the needed CIL, remember that the .NET Emitter classes may insert more opcodes than what you were expecting. Of course, if .NET supported design-by-contract, I would've seen the post-condition in BeginCatchBlock() and realized that leave is inserted into the CIL stream. The SDK does not document this, and technically DBC wouldn't require that this post-condition be stated either...but I think if DBC was added to .NET, people would start to document these kinds of conditions and behaviors in their assemblies where they belong.

Blog History