Run FxCop from Code

February 10, 2007

tags:
4 comments

As a very general introduction, FxCop is a code analysis tool developed by Microsoft that generates errors and warnings that should be part of the build process.  For example, existing FxCop rules can warn you about poorly performing constructs, about code that does not abide to naming conventions, about code that is not properly globalized or secure, and many more.  It is also possible to develop custom rules using the FxCop SDK, or download existing rules written by other developers.  Here’s a couple of links that have a collection of rules (I bet there are more being added by the minute as people are getting accustomed to the idea of managed code analysis):


http://dotnetjunkies.com/WebLog/tshak/archive/2005/04/12/65389.aspx
http://davidkean.net/archive/2005/02/19/341.aspx


For those of you who are aware of the tool, but have always used FxCop only as part of the Visual Studio Team Suite (i.e. using the Code Analysis tab on the project settings), you should probably be aware of the fact that FxCop offers a command line interface, in the form of the FxCopCmd.exe stand-alone executable.  Note that even if you don’t have VSTS, you can still use FxCop – it’s a free download.  Finally, the definitive resource to look for answers is the FxCop team blog.


A few weeks ago I’ve encountered the need to measure the performance of some custom FxCop rules on a project I’m consulting for.  These rules rely heavily on metadata that is obtained using reflection, which is not the standard FxCop way of looking at code.  FxCop has a means of analyzing assemblies and types using a so-called “introspection engine”, that looks at the IL directly, without loading the assembly (using Assembly.Load or the similar).  Some of the existing custom rules seemed to provide feasible performance, especially on small projects, and some of the rules caused Visual Studio to hang for minutes in a row while analysis was being performed.


So what I really needed to do is a means to run FxCop from code, so that I can easily measure the time it took to perform analysis for each of the rules.  The only resource I could find on the net was this forum question, which doesn’t really help.  Therefore, I started poking around with Reflector, to finally produce the following fragile code.  In spite of its being fragile, I believe this is the only resource on the web that provides a working implementation that does not invoke FxCopCmd.exe to analyze an assembly.  However, use it at your own risk – this is an undocumented scenario and all the APIs used below are subject to change in any future release (this is based on the version of FxCop that comes with VSTS).


using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Xsl;
using Microsoft.FxCop.Common;
using Microsoft.FxCop.Sdk;

namespace RunFxCopFromCode
{
struct RuleResultData
{
public string RuleName;
public long ElapsedMs;
}

class FxCopExecutor
{

public static RuleResultData Execute(string targetLocation, string ruleLocation, string referencesDirectory,
string specificRuleCategory, string specificRuleId)
{
RuleResultData result;
result.RuleName = String.Empty;
result.ElapsedMs = -1;

Stopwatch stopper = null;
try
{
ExceptionCollection exceptionCollection = FxCopOM.Initialize();
if ((exceptionCollection != null) && (exceptionCollection.Count > 0))
{
foreach (Exception exception in exceptionCollection)
{
Console.WriteLine(“* “ + exception.Message);
Console.WriteLine(exception.StackTrace);
}
}

FxCopOM.Project = new Project();
FxCopOM.Project.Options.SharedProject = false;

FxCopOM.Project.Targets.AddReferenceDirectory(referencesDirectory);
TargetFile file = new TargetFile(targetLocation, FxCopOM.Project.Targets);
FxCopOM.Engines.LoadTargets(file);
FxCopOM.Project.Targets.Add(file);
RuleFile rule = new RuleFile(ruleLocation, FxCopOM.Project.RuleFiles);
rule.CheckAllChildren(true);
FxCopOM.Project.RuleFiles.Add(rule);

FxCopOM.Project.Options.Stylesheet = string.Empty;

if (!String.IsNullOrEmpty(specificRuleCategory) && !String.IsNullOrEmpty(specificRuleId))
{
rule.CheckAllChildren(false);
Rule specificRule = FxCopOM.Project.AllRules.Find(specificRuleCategory, specificRuleId);
specificRule.Enabled = true;
specificRule.Checked = true;
result.RuleName = specificRule.DisplayName;
}

stopper = Stopwatch.StartNew();
FxCopOM.Engines.Analyze(FxCopOM.Project, true);
stopper.Stop();
if (!FxCopOM.Project.AnalysisResults.AnalysisOccurred)
{
Console.WriteLine(“Analysis was not performed”);
}
if (FxCopOM.Project.AnalysisResults.Exceptions.Count > 0)
{
Console.WriteLine(“Exceptions occurred during analysis”);
}
if (FxCopOM.Project.AnalysisResults.RuleExceptions.Count > 0)
{
Console.WriteLine(“Rule exceptions occurred during analysis”);
}

string reportFileName = Path.Combine(@”C:\Temp”, “Report” + specificRuleId);
reportFileName = Path.ChangeExtension(reportFileName, “.xml”);
FileStream reportStream = new FileStream(reportFileName, FileMode.Create);
FxCopOM.Project.SaveReport(reportStream, string.Empty, false, Encoding.ASCII);
XmlDocument reportXml = new XmlDocument();
reportStream.Position = 0;
reportXml.Load(reportStream);
reportStream.Close();

int numMessages = reportXml.SelectNodes(“//Message”).Count;
int numExceptions = reportXml.SelectNodes(“//Exception”).Count;
if (numMessages > 0)
{
Console.WriteLine(numMessages + ” messages”);
}
if (numExceptions > 0)
{
Console.WriteLine(numExceptions + ” exceptions”);
}

if ((FxCopOM.Project != null) && FxCopOM.Project.ContainsBuildBreakingMessage)
{
Console.WriteLine(“Build breaking message encountered”);
}
}
catch (FxCopException fxCopException)
{
Console.Error.WriteLine(fxCopException.Message);
}
catch (Exception exception)
{
Console.Error.WriteLine(exception.GetType().FullName + “: “ + exception.Message);
Console.Error.WriteLine(exception.StackTrace);
}
result.ElapsedMs = stopper == null ? -1 : stopper.ElapsedMilliseconds;
return result;
}
}
}


If the above code looks horrible in your browser, you can use the FxCopExecutor.zip attached to this post.  (It is zipped because the blog doesn’t allow .cs extensions.  It only contains a single .cs file inside.)  Also bear in mind that this is merely a proof of concept, and not a fully working example.


To wrap us this post, there’s another interesting point worth mentioning.  While I was writing this code, Roy Osherove noted that except for measuring the performance characteristics of the various rules, this POC code can be used to develop a testing framework for FxCop rules.  In the project I just mentioned, the “testing” framework for the rules includes a PERL script that executes FxCopCmd.exe over a set of testing assemblies, and compares the XML files generated to ensure they are identical.  This is obviously quite fragile, and a more detailed framework can be developed.  This seems like a relatively unexplored subject, so you are more than welcome to explore it.  :-)

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

4 comments

  1. amigaFebruary 10, 2007 ב 5:28 PM

    Very interesting & useful. Great post.

    Check GUYB blog, he has a post about how to paste code to your blog post to keep coloring & indentation.

    Reply
  2. laserbaronenOctober 22, 2007 ב 1:43 PM

    Error 1 ‘Microsoft.FxCop.Common.FxCopOM’ is inaccessible due to its protection level

    ;/

    Reply
  3. mloganJuly 16, 2008 ב 8:29 PM

    Same here. Trying this in VB.

    Error 1 ‘Microsoft.FxCop.Common.FxCopOM’ is inaccessible due to its protection level

    Reply
  4. Sasha GoldshteinJuly 19, 2008 ב 6:16 PM

    You’re too late to try it. In Visual Studio 2008 Microsoft has changed the code analysis assemblies. Not only did they turn almost everything internal, but the public-surface APIs changed as well.

    Reply