Un-swallowing Swallowed Exceptions with Fody and IL Weaving

You ever take over a codebase where the previous developer liked to ignore exceptions? You ever have to track down a production issue while the useful stack traces from those swallowed exceptions disappeared into the abyss?…

A basic example would be something like this:

public void DoSomething() {
    try {
        //do stuff
    } catch (Exception ex) {
        Debug.WriteLine("Error message: " + ex.Message);
    }
    return;
}

Or this:

public void DoSomething() {
    try {
        //do stuff
    } catch (Exception ex) { }
}

How can you clean this up in an app where this is done all over the place?

Don’t let anyone be lazier than you

Let’s show your predecessor who the real lazy developer is in this town.

Enter Fody, a powerful IL (Intermediate Language) Weaving tool by Simon Cropp.

Fody is a layer on top of Mono.Cecil that makes it easy to manipulate IL during the build process without the need for “plumbing code [that] involves knowledge of both the MSBuild and Visual Studio APIs.” (Fody Github page)

Mono.Cecil, written by Jb Evain, allows you to:

  • Analyze .NET binaries using a simple and powerful object model, without having to load assemblies to use Reflection.
  • Modify .NET binaries, add new metadata structures and alter the IL code.

“In simple English, with Cecil, you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly.” (Mono.Cecil project page)

So Fody lets us add, remove, and manipulate compiled IL code using Mono.Cecil’s APIs. With it’s add-in model we can use, extend, and write our own “weavers” to help us be even lazier than we already are.

Leveling up our laziness (in a good way)

So if we wanted to start cleaning up the swallowed exception mess without any help, we’d probably go through each method and delete the try/catch blocks, allowing the exceptions to bubble-up and make themselves known.

After that, we might implement a logging framework of choice. My logging framework of choice happens to be Serilog. Or we might add a monitoring solution like Stackify.

What if we could just add an attribute to “un-swallow” exceptions while you needed to fix something quickly?…

I decided to write a Fody add-in for that called UnSwallowExceptions.Fody.

Install via Nuget:

Install-Package UnSwallowExceptions.Fody

To un-swallow an exception for a method, simply add the attribute [UnSwallowExceptions]:

[UnSwallowExceptions]
public void Swallowed_exception_to_be_unswallowed() {
    try {
        throw new Exception();
    }
    catch (Exception) {

    }
}

The add-in in will locate any method with this attribute and weave an additional rethrow instruction into the IL code generated by the compiler. So originally, the IL code for our swallowed exception looked like this:

.method public hidebysig instance void
Swallowed_exception() cil managed
{
.maxstack 1

IL_0000: nop
.try
{
  IL_0001: nop

// [18 9 - 18 31]
  IL_0002: newobj       instance void [mscorlib]System.Exception::.ctor()
  IL_0007: throw
} // end of .try
catch [mscorlib]System.Exception
{

// [20 7 - 20 27]
  IL_0008: pop
  IL_0009: nop
  IL_000a: nop
  IL_000b: leave.s      IL_000d
} // end of catch
IL_000d: ret

} // end of method OnException::Swallowed_exception

With the [UnSwallowExceptions] attribute applied, the IL code looks like this:

.method public hidebysig instance void
Swallowed_exception_to_be_unswallowed() cil managed
{
.custom instance void [UnSwallowExceptionsReferenceAssembly]UnSwallowExceptions.Fody.UnSwallowExceptionsAttribute::.ctor()
  = (01 00 00 00 )
.maxstack 1

IL_0000: nop
.try
{
  IL_0001: nop

// [53 9 - 53 31]
  IL_0002: newobj       instance void [mscorlib]System.Exception::.ctor()
  IL_0007: throw
} // end of .try
catch [mscorlib]System.Exception
{

// [55 7 - 55 27]
  IL_0008: pop
  IL_0009: nop
  IL_000a: nop

// [57 9 - 57 15]
  IL_000b: rethrow
} // end of catch
IL_000d: ret

} // end of method OnException::Swallowed_exception_to_be_unswallowed

Notice the rethrow instruction inserted before the // end of catch line.

The C# method, if you were to decompile it, would look like this:

public void Swallowed_exception_to_be_unswallowed()
{
  try
  {
    throw new Exception();
  }
  catch (Exception ex)
  {
    throw;
  }
}

One step further with Logging

So now that you have un-swallowed the exceptions in a method, what can you do with them? Let’s get them to start logging to the team’s logging infrastructure. I use Serilog with Seq.

With IL Weaving, we can keep being lazy and still add this additional logging code using just one more Fody add-in - Anotar.

Anotar “Simplifies logging through a static class and some IL manipulation.”

So we can install Anotar’s Serilog package with:

Install-Package Anotar.Serilog.Fody

We want to make sure UnSwallowExceptions.Fody comes before Anotar.Serilog in our FodyWeavers.xml file (created when we install the Nuget package):

<?xml version="1.0" encoding="utf-8"?>
<Weavers>
  <UnSwallowExceptions />
  <Anotar.Serilog />
</Weavers>

And finally, we can add the [LogToErrorOnException] attribute after setting up a Serilog logger:

class Program {

    static Program() {
        var log = new LoggerConfiguration()
            .WriteTo.LiterateConsole()
            .CreateLogger();
        Log.Logger = log;
    }

    static void Main(string[] args) {
        DivideByZero();
        Console.ReadKey();
    }

    [UnSwallowExceptions, LogToErrorOnException]
    public static void DivideByZero() {
        try {
            int a = 10, b = 0;
            Console.WriteLine(a / b);
        }
        catch (Exception) { }
    }
}

I’ll spare you the IL code, but after applying both IL weavers to the DivideByZero() method, it looks like this:

public static void DivideByZero()
{
  try
  {
    try
    {
      Console.WriteLine(10 / 0);
    }
    catch (Exception ex)
    {
      throw;
    }
  }
  catch (Exception ex)
  {
    string messageTemplate = string.Format("Exception occurred in 'Void DivideByZero()'. ");
    if (Program.AnotarLogger.IsEnabled(LogEventLevel.Error))
      Program.AnotarLogger.Error(ex, messageTemplate, (object[]) null);
    throw;
  }
}

This can save you some time if there is a lot of exception swallowing throughout your app. Rather than having to delicately search around the code and remove try/catch blocks, all you have to do is add a couple attributes and start fixing the exceptions being thrown.

Summary

We started with inheriting an application that had tons of swallowed exceptions. Doing this was hiding potentially valuable information that could be addressed if these exceptions were otherwise allowed to bubble up to let us work on them.

One way to resolve this in the short term is to use IL Weaving provided by the Fody library by Simon Cropp. Fody makes it easy for us to modify IL code during the build process and do some really neat things. Check out the list of available add-ins here.

I wrote a Fody add-in that can be used to address our problem with swallowed exceptions called UnSwallowExceptions.Fody. Check out the Github repo here. It can be used alongside Simon Cropp’s Anotar for a convenient blow-everything-up-and-log-it approach to debugging.

Hope you enjoy and this helps you out some day! As always, make sure you sign up for my newsletter to get helpful articles like this sent weekly to your inbox!



Related Posts:


Sources

Tweet
comments powered by Disqus