Sunday, December 05, 2004

Reflector.CodeMetrics dynamically loads code metrics tabs at runtime by using Reflection. This means that anybody can write his own metric whithout recompiling CodeMetrics.... an Addin API inside another AddIn api :)

Here's a quick review of the steps to get your new cool code metric started. In this example, we will display the number of arguments per method (simple example).

Setting up the project

Start a new assembly project, add references to Reflector.exe, Reflector.CodeMetrics.dll and Reflector.Helpers.dll

Creating a new CodeMetric

All code metrics implement the ICodeMetric interface (this what the CodeMetricManager looks for):

public interface ICodeMetric : IServiceComponent

{

string Name { get;}

string FullName { get;}

ComputationState State { get;}

DataTable Result { get;}

Object FindItem(DataRow row);

bool Enabled { get;set;}

CodeMetricLevel Level { get;}

event ComputationProgressEventHandler Progress;

void Compute();

void Abort();

}

Of course, you don't have to implement it from scratch. You can use the abstract base class CodeMetricBase to get started:

public class ArgumentCountCodeMetric : CodeMetricBase
{
    // setting up name, full name and level of metric
    public ArgumentCountCodeMetric()
    :base("AC","Argument Count",CodeMetricLevel.Method)
    {}

    // adding columns
    protected void AddColumns()
    {
       this.AddColumn("Argument Count");
    }

    ...
}

It is important to note that we specify that the metrics target Method (can also target Type, Module). The AddColumns method is used to all the columns you want to show in the grid.

Add the metric computation

Time to get to the real fun, computing the metric... First, we provide a method GetTestCount that returns the number of steps. This number is used to scale the progress bar:

private int GetStepCount()
{
    int count = 0;
    foreach (IAssembly assembly in this.Services.AssemblyLoader.Assemblies)
    {
       if (this.Services.CodeMetricManager.IsIgnored(assembly))
           continue;
       foreach (IModule module in assembly.Modules)
            count += module.Types.Count;    
    }
    return count;
}

It is important to note that we check for each IAssembly if it has to be ignored. This is directly linked to the checkbox control on the CodeMetrics page. Note that we coun the types only, if you show progress on methods, almost all the CPU time is spent to update the controls. Next, we write the method that create a row in the grid from a IMethodDeclaration:

private void ComputeMethod(IMethodDeclaration method)
{
    IMethodBody body = method as IMethodBody;
    if (body == null)
        return;
    this.AddRow(
        method,
        body.Parameters.Count 
        );
}

The AddRow methods will add a row in the underlying DataTable, etc...

Implementing Compute

We have all the pieces, we just need to implement Compute:

public override void Compute()
{
    base.Compute();
    int stepCount = GetStepCount();
    int count = 0;
    foreach (IAssembly assembly in this.Services.AssemblyLoader.Assemblies)
    {
        if (this.Services.CodeMetricManager.IsIgnored(assembly))
            continue;
        foreach (IModule module in assembly.Modules)
        {
            foreach (ITypeDeclaration type in module.Types)
            {
                this.OnProgress(
                    new ComputationProgressEventArgs(
                        count++,
                        stepCount,
                        "Analysing {0}",
                        type
                        )
                     );
                foreach (IMethodDeclaration method in type.Methods)
                {
                    if (this.CheckForAbort())
                        return;
                    ComputeMethod(method);
                }
            }
        }
    }
    this.Finished();
}

The metric is ready, we just need to register it to the manager when the Addin is loaded.

Setting up a package and registering CodeMetrics

Reflector looks for IPackage class when loading Addins. Reflector.Helpers contains a smart implementation of that addin that will automatically load the code metrics the assembly contains, so all you have to do is to inherit from PackagePage (well almost):

namespace Reflector.CodeMetrics
{
    public sealed class CodeMetricPackage : PackageBase
    {
        [ReflectorWindow(
        Name = "Dummy control to make Reflector happy",
        Caption = "Dummy control to make Reflector happy"
        )]
       [ReflectorCommandBar(CommandBarTarget.Tools)]
       private UserControl dummy = new UserControl();
    }
}

If you do not actually load a new window, Reflector gets pissed and unload the addin. Therefore, just add a dummy window. At this point, recompile (makes sure you target the right .Net framework), load and enjoy.

Debugging

To debug, just start Reflector as external process and break point on your code.

posted on Monday, December 06, 2004 6:48:00 AM UTC  #    Comments [11]
Tracked by:
"build dune buggy" (online) [Trackback]
"rainbow bridge poem" (online) [Trackback]
Monday, June 06, 2005 4:52:53 PM UTC
Thanks a lot for this great Addin an explanation of way to use it!
<br>
<br>What version of <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a> API do you use? I have no Parameters collection in IMethodBody interface even in <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a> 4.1.18.
Alex Titovich
Monday, June 06, 2005 4:52:53 PM UTC
Ooops, I did the example on the fly. You should get the parameters from the IMethodDeclaration...
Jonathan de Halleux
Monday, June 06, 2005 4:52:53 PM UTC
I'm trying to calculate some specific variant of cyclomatic complexity and I've got stuck with with following problem. Cyclomatic complexity in general is calculated with the IInstruction.Code checking for Code_Branch. And I found no opportunity to detect the operand of such instruction (I need to calculate quantity logical operations used in operand of branching instruction).
<br>
<br>May be you know some way to recieve operand of branching instruction?
Alex Titovich
Monday, June 06, 2005 4:52:53 PM UTC
Well, isn't IInstruction.Operand what you are looking for ?
<br>
Jonathan de Halleux
Monday, June 06, 2005 4:52:54 PM UTC
It looks like it is. I've found it too, thanks!
<br>But I'm totally failed with any attempts to get it correctly. If I'm not mistaken <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a> API should allow converting it to Expression (or to ConditinalExpression which is better for my purpose). But what is a correct way to organise this conversion - after hours spent to this &quot;small problem&quot; it seems I've been &quot;lost in a forest&quot; :)
Alex Titovich
Monday, June 06, 2005 4:52:54 PM UTC
Could you please prompt me where in the <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a>.Graph code I could find the place where vertex is populated with conditional instruction and it's operand?
Alex Titovich
Monday, June 06, 2005 4:52:54 PM UTC
Sorry that code is not public.
<br>
<br>If you want to have access to the decompiled tree, you need to work on the IMethodDeclaration. IMethodBody will give you IL only.
<br>
<br>IMethodDeclaration method = ... ;
<br>// decompiling
<br>IMethodDeclaration decompiledMethod = this.Services.ActiveVisitor.VisitMethodDeclaration(method);
<br>
<br>Use the Visitor class from Relfector.Helpers to explore thet decompiled tree.
<br>
<br>You can also get the statement graph that I build. It is called StatementGraph (how original) and you can build it using StatementGraphPopulator.
Jonathan de Halleux
Monday, June 06, 2005 4:52:55 PM UTC
Thanks once again, you really take me out of a dead cycle.
<br>
<br>If I understand correctly, Visitor is just a abstract class to load piece of decompiled code. And it is necessary to provide some methods to explore the code? Is it possible to make some short description of data structures where decompiled statemants are stored in?
<br>
<br>Sorry for the inconvenience with my constant questions, but I don't know any other person or source of information to get the answers. Inspite of great supports that <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a> and it's addins provides in source browsing it is still diffcult enough to work without manuals :)
<br>
Alex Titovich
Monday, June 06, 2005 4:52:55 PM UTC
Maybe I need to tell the aim of all my interest to the metrics :)
<br>
<br>For this moment I have an academic interest for the following problem. I suppose to select some metrics that will allow rough estimation and comparison of obfuscation results. So in order to have sample results for the calculation, I need a tool to calculate my own metric. And the best way to develop this tool, I think, is to base it on the <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a>.CodeMetric.
<br>
<br>Maybe it is possible to get some documentation for <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a>.CodeModel and <a title="Reflector" href="http://www.aisto.com/roeder/dotnet/" target="_blank">Reflector</a>.CodeMetric as an &quot;Academic Licenses&quot;? :)
<br>
Alex Titovich
Monday, June 06, 2005 4:52:55 PM UTC
Alex, we can continue this conversation by email. Use the Contact Me link on this page.
Jonathan de Halleux
Monday, June 06, 2005 4:52:55 PM UTC
Wow, superb!
<br>Thanks.
Diana
Comments are closed.