Analysing assemblies and finding out possible leaks by not disposing of objects properly

Today’s post is about… A command line utility I wrote a few days back for analysing and finding out possible leaks by not disposing of objects properly.

Before we get into the nuts and bolts of the utility, please find attached two files which are:

  1. MSIL Instruction Set Specification (Required to do any MSIL manipulation)
  2. Utility’s Source code

It’s well-known that every single object that implements IDisposable must be disposed by:

  1. Calling the Dispose method explicitly
  2. Enclosing the object that implements IDisposable within an using statement

Said that, let’s have a look at the code snippet shown below

public class LeakyClass {

    public void LeakyMethod() {

        var conn1 = new SqlConnection();

        var conn2 = new SqlConnection();

        conn2.Dispose();


        using (var conn3 = new SqlConnection()) {

            // Do something here...

        }

    }

}

This is the generated MSIL from the code snippet

Figure12

As you can see, three objects of type SqlConnection are created and two of them are disposed. When enclosing the object within an using statement what really happens is that a try… finally…  block is produced. As you can see the Dispose method is a callvirt which is a “Call to a virtual method” and it’s virtual because it was implemented from the IDisposable interface. MSIL similar to assembler has some Op codes, actually there are 256 (0x100) Op codes being [Dec = 111 | Hex =0x6f] (callvirt) and this is the one we’ll use in our utility.

The way as our utility works is described as follows:

  1. An assembly is loaded for analysis as specified in argument (eg.:  -a “C:myassembly.dll”)
  2. The Inspect method is called which performs the analysis on the modules & types referenced in the assembly
    private static void Inspect(Assembly current) {

     var flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

     var stats = new Dictionary<string, DisposableStats>();

     current.GetModules().ToList().ForEach(x => x.GetTypes().ToList()

        .ForEach(y => y.GetMethods(flags).ToList().ForEach(z => InspectMethod(x, z))));

    }

  3. The InspectMethod is called only processing the variables (objects) which implement IDisposable
    private static void InspectMethod(Module module, MethodInfo method) {

      var body = method.GetMethodBody();

    
    

       if (body != null) {

            var variables = body.LocalVariables;

            var disposable = variables.Where(x => x.LocalType.GetInterfaces()

                             .Where(y => !string.IsNullOrEmpty(y.Name) &&

                                    y.Name.Equals("IDisposable")).FirstOrDefault() != null);

    
    

            var count = disposable.Count();

            var key = new MethodDescription(method.ReflectedType.ToString(), method.Name);

    
    

            if (count > 0 && !stats.ContainsKey(key))

                stats.Add(key, IdentifyDisposableObjects(body.GetILAsByteArray(), count, module));

        }

    }

  4. Then we call IdentifyDisposableObjects method that takes as an argument an array of bytes containing the MSIL of the method. We take into account only callvirt (0x6F) op code.
    private static DisposableStats IdentifyDisposableObjects(byte[] ilasm, int disposable, Module module) {

        int position = 0, offset = 0, token = 0;

        var retval = new DisposableStats(disposable);

    
    

        while (position < ilasm.Length) {

            var method = string.Empty;

            var value = ilasm[position++];

    
    

            // Is it a callvirt? 

            if (value == 0x6F) {

                offset = position - 1;

                token = ReadInteger(ilasm, ref position);

    
    

                if ((!string.IsNullOrEmpty(method = module.ResolveMethod(token).Name)) &&

                    method.Equals("Dispose", StringComparison.OrdinalIgnoreCase))

                     retval.DisposedCount++;

            }

          }

      return retval;

    }

  5. We do this for every method in the assembly, and add this information to a dictionary that’s later dumped as a report.

Figure13

If you have a copy of reflector, please feel free to check the results of the report with reflector’s disassembly… You’ll be amazed with the amount of objects that are being left behind and not being disposed.

Regards,

Angel

0 thoughts on “Analysing assemblies and finding out possible leaks by not disposing of objects properly”

Leave a Reply

Your email address will not be published. Required fields are marked *