Bugslayer

Bad Code? FxCop to the Rescue

John Robbins

Code download available at:Bugslayer0406.exe(167 KB)

Contents

The Gestalt of FxCop
Common Errors
Setting Up Your Project
Introduction to FxCop Rules
Using the Introspection engine
Before You Begin Analysis
The Important Check Method
Wrap-up
Tips

When you've got bad code, there's only one thing you can do when they come for you: call FxCop. Some of you may have already used this excellent free tool from Microsoft, but many developers haven't because, for some unexplained reason, it's hidden deep down in the bowels of the Microsoft Web site at https://www.microsoft.com/en-us/download/details.aspx?id=6544. (FxCop, if you don't know, is a code analysis tool that checks managed code assemblies for conformance to Microsoft® .NET Framework design guidelines.) The big news is that the industrious FxCop team has just released a new version, 1.30, that has many nice improvements and is considerably faster at analyzing large applications. The FxCop team let me take an early peek at the new version so I could write this column close to the time they release it to the world.

FxCop is so important that I expect to spend two columns discussing how to use it and, most importantly, how to write your own rules. This month I want to start out with an introduction to FxCop. I will talk mainly about how to properly analyze FxCop errors in your code and how to integrate it into your daily build. Before I'm finished, I'll show you how to build your own FxCop analysis rules. Because rule development is such an extensive topic, I'll discuss how to build advanced rules in my next column.

The Gestalt of FxCop

Microsoft has obviously made a huge investment in .NET and in promulgating best .NET coding practices through the .NET Framework Design Guidelines, which you can find at Design Guidelines for Class Library Developers. Internally, Microsoft wanted to make sure their own developers followed the rules for consistency and maintainability. To enforce the Design Guidelines they devised a solution, out of which sprang FxCop.

While the FxCop team could have taken a source-code-analysis approach, like the tools of old, they decided to use the strength of .NET to do much of the heavy lifting. The first versions of FxCop used reflection so they didn't have to worry about finding and reading classes in an assembly. The bulk of the infrastructure work was involved in cracking the Microsoft Intermediate Language (MSIL) and providing an extensible scheme to write rules.

What's new in FxCop 1.30 is that it now performs analysis through a technique called Introspection. The use of the Introspection engine allows for much faster analysis and supports multithreaded analysis. Unlike the Reflection engine from previous versions, in the Introspection engine the assemblies you're analyzing are not locked so you won't need to shut down FxCop to do a fix and recompile of those assemblies. Finally, the Introspection engine offers a much richer analysis infrastructure compared to the Reflection engine. Those of you who have built rules using the Reflection engine from previous versions will be glad to know that your code investment is safe as FxCop still fully supports the Reflection engine. To update your rules, you'll need to change a few of the base class names you derived from and do a recompile.

FxCop has over 200 rules that cover everything about your assemblies from ensuring that you use COM interop correctly, to ensuring proper globalization, to enforcing rules for writing high-performance code. The FxCop help file, which is part of the installation, documents all of the existing rules; I'll refer you there to read about them. While you might think that the existence of more than 200 rules would mean that there's no need for more, you'll see in a minute that's not the case at all.

If you've never used FxCop, you should probably stop reading right now and give it a whirl because for the rest of the column I'll assume you've at least run it once across your application's binaries. If you have any application more complicated than "Hello World" don't be surprised if you're staring at a ton of errors in the message pane the first time you run FxCop. The good news is you found some errors; the bad news is you have to fix them. Figure 1 shows FxCop pointed at one of my assemblies.

Figure 1 FxCop in Action

When it comes to fixing those errors the first time you run FxCop, it's just like the old joke about how you eat an elephant: one bite at a time. Figure 2 defines the icons shown in the message pane sorted in order of most to least importance. The trick to getting a handle on FxCop-reported errors is to sort the Level column in the message pane so that the critical errors are at the top of the list. Start working through those and move on to the errors. In the big scheme of things, the difference between a critical error and an error is a subjective one. I wish the FxCop team had used a simpler scheme consisting only of errors, warnings, and informational messages as it would have simplified the UI and made life easier for rule developers.

Figure 2 FxCop Error Levels

Icon Name Description
Critical error Errors that prevent the code from working. These are the errors you always fix first; they must never be excluded.
Error General errors that don't affect usability and behavior. Only exclude these errors in extremely rare circumstances.
Critical warning Problems that don't affect code behavior but can cause issues with maintainability and compliance with the Design Guidelines. You should not exclude these errors.
Warning Lesser problems that affect stability, maintainability, and correctness as dictated by the Design Guidelines.

FxCop offers excellent advice on how to fix problems, but one thing that confuses folks is determining when an error that's reported by FxCop should be excluded completely. In general, you should exclude errors only very rarely. If you do exclude an error, FxCop now makes it mandatory to include a note about why you are excluding it. On my teams, the Project Manager/Team Lead is responsible for checking that every excluded error has a solid reason for its exclusion.

Common Errors

Some of the errors you'll see in your assemblies will not be your fault. For instance, some of the code generated by the IDE forms designer does not follow the Design Guidelines at all. If you see errors in the Windows® Form's InitializeComponent method, you can safely exclude them. Some other errors concern ASP.NET naming conventions. The word is that for ASP.NET 2.0 and Visual Studio® 2005, Microsoft is scrubbing all the wizards and code generators with FxCop to avoid these clashes.

I want to point out a couple of tricks for fixing particular errors faster and understanding their consequences. When you run FxCop against your assembly, the first error you'll probably see is the "Assemblies specify permission requests" error. Fixing this error is not as easy as most because you'll need to inspect your code closely to determine exactly which permissions you need and which you can refuse. When Visual Studio 2005 ships, it will have a security analysis tool that will identify the minimum security permissions your app will need, making permissions trivial to set. Until this tool ships, it's a good idea to have a coworker analyze your code to ensure your permissions are set appropriately.

FxCop has an aggressive spell checker and will probably generate many errors simply because it doesn't know your company name and other names in your code. Fortunately, in the FxCop directory is a file called CustomDictionary.xml where you can add words and acronyms. (Obviously, you'll want to keep your team's version of CustomDictionary.xml in your version control system along with your FxCop file so everyone can benefit.) You can also flag words as deprecated or inappropriate. Earlier versions of FxCop only read the CustomDictionary.xml file out of the FxCop installation directory; version 1.30 looks for the file in the same directory as your project file. Figure 3 shows CustomDictionary.xml using a variety of fields.

Figure 3 Dictionary

<Dictionary> <Words> <Recognized> <Word>Bugslayer</Word> <Word>uncategorize</Word> <Word>fxcopbug</Word> <Word>stylesheet</Word> <Word>gotdotnet</Word> <Word>uninstantiated</Word> <Word>rethrows</Word> <Word>aspx</Word> <Word>ndpsec</Word> <Word>typeof</Word> <Word>readonly</Word> <Word>Sta</Word> <Word>Callee</Word> <Word>Callees</Word> <Word>Pdb</Word> <Word>Opcode</Word> <Word>&amp;amp</Word> <Word>&amp;gt</Word> <Word>&amp;lt</Word> </Recognized> <Deprecated/> <Inappropriate/> </Words> <Acronyms> <CasingExceptions> <Acronym>CJK</Acronym> </CasingExceptions> </Acronyms> </Dictionary>

You can see the Deprecated and Inappropriate elements under the Words element, but at the time of this writing the FxCop rules supplied by Microsoft do not read those sections. However, there's nothing stopping an ambitious developer from writing a new rule that will read and process them.

Setting Up Your Project

Once you've taken care of the initial round of errors and warnings in your assemblies, you'll want to examine how your projects are set up. Along with the GUI version of FxCop, there's a console version, FxCopCMD, which you should use so you can have FxCop run after every build to ensure no additional problems have edged their way into your code. In fact, teams at Microsoft are now running FxCop against their daily builds. That alone should make it mandatory for you as well.

While FxCopCMD will work just fine by pointing it at an assembly, you'll want to use a previously created FxCop project with FxCopCMD so your exclusions and additional rule files are always used. There's a big question as to what type of project you want for your application. On one hand, it's quite convenient to have a single project for all the assemblies in your application. That way there's a single call you'll have to make as part of your build script. The one master project is also the one place all exclusions reside for your whole application. It's also very convenient if you want to add a new rule and have everyone use it; you only have to add it to the one FxCop project and everyone will automatically be using it.

The biggest drawback to using one large master FxCop project is that if you have an application that has lots of assemblies, running FxCop will be a lengthy operation when you want to check only a few assemblies. While you can always uncheck assemblies you don't want analyzed (in the Targets tree control), that can cause a big problem. For example, if you pull the master FxCop project for the application from version control to make some exclusions, you might unintentionally leave those assemblies unchecked when you return the file. Then your daily build will analyze only those few assemblies and nothing else, defeating the purpose of FxCop.

While going to individual FxCop projects for each assembly is faster, it has its drawbacks as well. First, it is now a bigger hassle to check all those individual files to ensure they contain additional rules files and that exclusions have been used properly. Second, you have to account for all those individual FxCop project files, and it's easy to forget to add new assemblies to the mix.

What I would really like is a tool that looks in your build directories and consolidates the individual assembly FxCop projects into a single master FxCop project. That would mean moving over all targets and exclusions from the individual files to the master file. My original plan was to write such a tool as part of this column, but I found that it was going to be more complicated than writing code to merge project files. Since I haven't built the merging utility yet, if you're interested in working on it, send me an e-mail and I can coordinate the project between us and other interested parties.

Until a merge utility exists, the best practice, in my opinion, is to have a single FxCop project for your entire application. In the meantime, I wrote a quickie tool, NoPartialAnalysis, to check that all targets have full analysis turned on. It performs a check of the Targets node in an FxCop project and if any of the nodes are unchecked, the tool exits with a return value of 1. Your daily build will fail on the bad return value so you know you're not getting the full FxCop check. Once you check the version control logs and find who made the bad move, you can apply the proper motivation so that person never does it again.

Overall, there's nothing too exciting about NoPartialAnalysis. It's really just XML manipulation. Note that NoPartialAnalysis properly handles both the normal compressed saved files and the uncompressed. The default is compressed, and I strongly suggest you use it. The only benefit to the uncompressed files is that you will fill up your hard disk and have an excuse to get a new one.

For many people, simply using FxCop with its default rules supplied with the download is sufficient enough. However, real developers will immediately think of a whole bunch of errors that they would love a tool to check for them automatically, so they'll be itching to write their own rules.

Introduction to FxCop Rules

If you've peeked at the FxCop SDK section of its help file, you've seen that there's only documentation for the Reflection engine. It mentions that the SDK for the Introspection engine will be posted later. While you can write powerful rules with the Reflection SDK, the Introspection engine has all the great features and it's drastically faster than the Reflection engine.

While I could have taken the easy way out and used the Reflection SDK to build reflection-based rules, I felt it was important to get started with Introspection. The obvious drawback is that the FxCop team did not document the Introspection engine at all, but that never stopped a determined developer armed with Lutz Roeder's phenomenal Reflector and its awesome .NET decompiler. Once I started poking at the Introspection engine and the rules that come with FxCop (see the <FxCop installation>\Rules directory for those assemblies), I quickly learned that it represents a giant step up in complexity.

While the Reflection engine provides a few helper functions, the bulk of the work those rules perform is through normal .NET reflection and in string manipulation as the underlying MSIL is returned as a string buffer. With Introspection, you can rely on the Introspection engine to do all of that work. That means I had to spend a lot more time looking at the existing rules in Reflector to see how they performed their magic in order to get started on specific rules. As you get into more complicated rules, that becomes a daunting task. Fortunately, with Reflector, it really becomes an exercise in code reading.

While you certainly can build cool rules without documentation, as you'll see there is one potentially more serious reason that the FxCop team might not have documented the Introspection engine. They might be planning to change the Introspection engine interfaces in the future and don't want folks using the existing interfaces. That's a perfectly valid reason and could present problems in the future. However, I'll take that chance, as I'll get the benefit of faster execution and no file locking today with the Introspection engine. Additionally, I'm willing to bet that the interfaces won't change too drastically because that would require the FxCop team to rewrite more than 200 existing rules for the Introspection engine. As anyone who's had to fix programs written five years ago knows, once you write some code it has a habit of sticking around for quite a while. The FxCop team was kind enough to confirm for me that the Introspection engine interfaces, except for two methods I'll discuss later, will be around for a long time. There are also plans to document the Introspection engine.

Using the Introspection engine

I wanted to point out all the issues with using the Introspection engine for building rules so you'd understand the ramifications. If the FxCop team does change the rules in the future, I'll make sure to update any rules I've developed and mention it in future Bugslayer columns. Since I use these rules in all of my production code, I have a stake in updating them on every FxCop release.

What you can do with a rule is limited only by your imagination. What makes FxCop so interesting is that you get access to everything in an assembly, from all attributes (applied to anything), right down to an individual parameter of a method. Even better is that you get very cool support from FxCop to get at the MSIL for a method as well as the call graph for callers and callees. You'll just need to make sure that the rule you want makes sense from the standpoint of analyzing the binary. For example, if you are thinking about considering a performance analysis rule, unless you can look for an exact MSIL or call pattern, it might be better to use source analysis for that type of rule. A good example of a performance rule that works because you're looking for a fixed pattern is the TestForEmptyStringsUsingStringLength rule you'll find in FxCop's PerformanceRules.DLL.

The basics of getting an Introspection engine rule started are straightforward and if you're familiar with the Reflection engine resource-based rules, you should feel right at home. In Figure 4, I combined the three files of the supplied EmptyFxCopRule file so you could follow along with me. The simple base class, BaseEmptyFxCopRule, exists solely to derive from the required BaseIntrospectionRule from the Microsoft.Tools.FxCop.Sdk.Introspection namespace. BaseIntrospectionRule assumes that the description text and related data resides in an embedded resource. Consequently, the three parameters required by the BaseIntrospectionRule constructor are the type name of the rule, the embedded resource file name that contains the rule descriptions, and the assembly that contains the embedded resource file. Since you'll most likely have more than one rule per assembly, having a simple base class like BaseEmptyFxCopRule means that you can hide the common resource information manipulation in the base class and require derived classes to specify the type name in calling the base class constructor.

Figure 4 Minimal FxCop Rule

BaseEmptyFxCopRule.CS

using System ; using Microsoft.Cci ; using Microsoft.Tools.FxCop.Sdk ; using Microsoft.Tools.FxCop.Sdk.Introspection ; namespace EmptyFxCopRule { [CLSCompliant(false)] public abstract class BaseEmptyFxCopRule : BaseIntrospectionRule { protected BaseEmptyFxCopRule ( String name ) : base ( name , "EmptyFxCopRule.RuleData" , typeof(BaseEmptyFxCopRule).Assembly ) { } } }

EmptyAssemblyRule.CS

using System ; using Microsoft.Cci ; using Microsoft.Tools.FxCop.Sdk ; using Microsoft.Tools.FxCop.Sdk.Introspection ; namespace EmptyFxCopRule { /// <summary> /// Summary description for EmptyRule. /// </summary> [CLSCompliant(false)] public class EmptyAssemblyRule : BaseEmptyFxCopRule { public EmptyAssemblyRule ( ) : base ( "EmptyAssemblyRule" ) { } public override Problem[] Check ( AssemblyNode theAssembly ) { return ( null ) ; } } }

RuleData.XML

<?xml version="1.0" encoding="utf-8" ?> <Rules> <Rule TypeName="EmptyAssemblyRule"> <Name> EmptyAssemblyRule: The prose name of the rule goes here. </Name> <Description> EmptyAssemblyRule: The short description goes here. </Description> <LongDescription> EmptyAssemblyRule: The long description goes here. </LongDescription> <GroupOwner> EmptyAssemblyRule: This is optional, but is generally the developer/team/company. </GroupOwner> <DevOwner> EmptyAssemblyRule: The team or individual responsible for the rule. </DevOwner> <Url> EmptyAssemblyRule: The URL for help containing the rule. </Url> <Resolution> EmptyAssemblyRule: The string that appears in the message panel pointing out the error. </Resolution> <Email> EmptyAssemblyRule: The optional email address of the rule author. </Email> <Message Certainty="99"> Warning </Message> </Rule> </Rules>

The second file, EmptyAssemblyRule.CS, shows a minimal rule that looks at assemblies. I'll talk more about the Check method later. The main thing to notice is that the name of the class is EmptyAssemblyRule. You can see that in its constructor, EmptyAssemblyRule passed that string into the base class.

The final file in Figure 4 is the embedded resource XML file. I did a little format tweaking so you could see the individual elements more easily. The key text is at the top where the TypeName attribute lists the name of the rule that the data applies. While you can probably discern the meaning of all the other elements, I found it valuable to add the EmptyFxCopRule assembly to an empty project, expand the tree for EmptyFxCopRule, and double-click on the EmptyAssemblyRule so you can see where the Rules Details dialog displays the various text elements.

Before You Begin Analysis

Before I get into the details of rule development, I want to point out a few things I encountered. First note that adding an XML file to the project does not add it as an embedded resource. Consequently, after adding the XML resource file that describes the rule, make sure you set the Build Action to Embedded Resource instead of Content. When creating new rules, even if they are additional rules in an existing assembly, always test to make sure it's set up correctly. It's simple to test a rule infrastructure by building your rule shell and adding it to an empty FxCop project. If FxCop reports an exception when loading your rule, it usually means you have an error in your XML resources. Unfortunately, FxCop just reports that you have bad XML, not which element is wrong, so finding it will take some trial and error.

When I first put the EmptyFxCopRule file together for this column, I couldn't get the actual EmptyAssemblyRule to show up underneath the assembly in the FxCop Rules tab. Since I was creating an empty rule, I had mistakenly used "Informational" instead of "Information" as the message type. Once I fixed my mistake, the rule showed up just fine. The Message element is not very forgiving when it comes to the name you specify—it must exactly match what's in the Microsoft.Tools.FxCop.Sdk.MessageLevel enumeration or you'll get an XML loading error in FxCop. Just so you have it, here's the definition from Reflector:

public enum MessageLevel { CriticalError = 1, CriticalWarning = 4, Error = 2, Information = 16, Warning = 8, }

One last thing I want to talk about here is the protection-level properties, TypeProtectionLevel, MemberProtectionLevel, and NestedTypeProtectionLevel, which you can override from BaseIntrospectionRule. Those members return an OR'd level specified by the Microsoft.Tools.FxCop.Sdk.ProtectionLevels enumeration (see Figure 5).

Figure 5 Protection Levels

public enum ProtectionLevels { Public = 1, Family = 2, Private = 4, Assembly = 8, FamilyAndAssembly = 16, FamilyOrAssembly = 32, NestedPublic = 64, NestedFamily = 128, NestedPrivate = 256, NestedAssembly = 512, NestedFamilyAndAssembly = 1024, NestedFamilyOrAssembly = 2048, All = 4095, None = 1073741824, }

The default OR'd values returned by those properties are listed in Figure 6 and you can see that they generally relate to the visibility levels associated with those items you can access from another assembly. You'll almost always go with the defaults, but I want to point out the ProtectionLevels enumeration properties as it's easy to forget they're there and you'll wonder why that private method you want to test isn't being passed to your Check method.

Figure 6 BaseIntrospectionRule Default Protection Levels

Property Default Protection Levels
TypeProtectionLevel Public
NestedTypeProtectionLevel Public | NestedPublic | NestedFamily | NestedFamilyOrAssembly
MemberProtectionLevel Public | Family | FamilyOrAssembly

The Important Check Method

As you've probably guessed, the Check method is where all the action is. The BaseIntrospectionRule class defines 15 different Check methods you can implement in your rules, most of which I've listed in Figure 7. Two overloaded Check methods not shown—one that takes a Method and Statement as a parameter and one that takes nine parameters—are deprecated and will be removed from future versions of the Introspection engine. As you scan down the list of check methods, you can see, for example, that if you want a rule that works on properties, you simply override the version Check that takes the Property type as a parameter. All the types such as Method, Property, and so forth come out of the Microsoft.Cci namespace defined in Microsoft.Metadata.Prototype.DLL, which can be found in the FxCop directory.

Figure 7 BaseIntrospectionRule Check Methods

public virtual Problem[] Check ( AssemblyNode theAssembly ) ; public virtual Problem[] Check ( Event theEvent ) ; public virtual Problem[] Check ( Field field ) ; public virtual Problem[] Check ( InstanceInitializer initializer ) ; public virtual Problem[] Check ( Member member ) ; public virtual Problem[] Check ( Method method ) ; public virtual Problem[] Check ( Method method , Parameter parameter ); public virtual Problem[] Check ( Module theModule ) ; public virtual Problem[] Check ( Property theProperty ) ; public virtual Problem[] Check ( Resource resource ) ; public virtual Problem[] Check ( StaticInitializer initializer ) ; public virtual Problem[] Check ( string namespaceName , TypeNodeList types ) ; public virtual Problem[] Check ( TypeNode type ) ;

When I started to write FxCop rules, the first ones I wanted to add involved assemblies and, fortunately, those are the easiest to get started with. All the rules I'll be talking about are in the Bugslayer.FxCop.DesignRules assembly in the code download available at the MSDN® Magazine Web site, so you may want to pull up this project and follow along. To create it, I simply followed the same techniques I found by using Reflector on the FxCop rules.

One point of contention I've had with the Design Guidelines is that it's not mandatory to include an XML documentation file with your assemblies. My guess is that since Microsoft did not include full documentation comments as a feature of Visual Basic® .NET and managed C++, it couldn't make it a hard requirement. However, all languages will support XML documentation files in Visual Studio 2005, and for Visual Basic .NET today there are numerous add-ins with that capability. I've made it a requirement in all of my projects, so I wanted an FxCop rule that will check that an assembly always has documentation associated with it.

This very important rule, AssembliesHaveXmlDocCommentFiles, turns out to be extremely simple to write. You can find the code in the eponymous .cs file. To show you how simple the rule is, Figure 8 shows the Check method that does all the work. As you can see, if you return null/Nothing in the Check method, that tells FxCop that you didn't find any errors. Returning an allocated Problems array signals that there are errors and tells you the text of the error. In Figure 8, I use the BaseIntrospectionRule.GetResolution method to get the Resolution element out of the AssembliesHaveXmlDocCommentFiles rule's XML file. In this case, the string is: " '{0}' is missing or has an invalid XML Doc Comments file." The format item allows me to put the name of the assembly in the error text, which I do by calling the Problem property's (a ProblemsCollection type from BaseIntrospectionRule) AsArray method. What's nice about the Problem property in the BaseIntrospectionRule is that if you have a rule that is recursive, you can add all the problems you've found in the code and take care of getting them all. With most rules only returning a single error string, you'll usually use code like what's specified in the error portion of Figure 8 to do the work.

Figure 8 Check Method for Assembly XML Comments

public override Problem[] Check ( AssemblyNode theAssembly ) { // First, check if this is a DLL library. if ( false == IsLibraryAssembly ( theAssembly.Location ) ) { // It's not a DLL so there's no need for an XML file. return ( null ) ; } // Build up the name to the XML file, which should be in the same // directory where the DLL is located. String XmlFile = Path.ChangeExtension ( theAssembly.Location , XMLExtension ) ; // The return value. Problem[] retvalue = null ; bool failedTests = false ; if ( true == File.Exists ( XmlFile ) ) { // The file exists so let's see if it's a proper and happy XML // file. try { XmlDocCommentsFileDictionary tempFile = new XmlDocCommentsFileDictionary ( XmlFile ) ; // I guess you could optionally check that the timestamps // are close here, but since the Visual Basic .NET comment // file can be built at a different time, I can't always be // assured the timestamps are close. } catch ( XmlException ) { failedTests = true ; } } else { failedTests = true ; } if ( true == failedTests ) { // Build up the error string to return. String[] arr = new String[1] ; arr[0] = Path.GetFileName ( theAssembly.Location ) ; retvalue = Problems.AsArray ( GetResolution ( arr ) ) ; } return ( retvalue ) ; }

While the AssembliesHaveXmlDocCommentFiles rule really doesn't do much with the Introspection engine, most of your code will. After struggling to figure out the gyrations necessary to use the Introspection engine code, it finally dawned on me how to think about it. I happened to look at the version comment for the Microsoft.Metadata.Prototype.DLL assembly, which contains the base class Standard Visitor for BaseIntrospectionRule, and it says: compiler-oriented replacement for System.Reflection and System.Reflection.Emit. That's when it hit me that the Introspection engine is simply another way of doing reflection. Of course, there's no documentation, but once I realized that fact, it became easier to visualize how to use the Introspection engine. Where reflection loads up the assembly and calls the types and other information in the assembly, the Introspection engine maps the assembly into memory and goes through the grind of walking all the internal tables by hand so it avoids locking the assembly like reflection does.

While my first rule, AssembliesHaveXmlDocCommentFiles, is relatively easy, I wanted to create other rules that would look to see if an assembly had the AssemblyTitleAttribute, AssemblyCompanyAttribute, AssemblyDescriptionAttribute, and AssemblyCopyrightAttribute all applied and not set to null values. Again, I feel all those attributes should always be set on any assembly in order for the assembly to be correct. At first I thought it was going to be pretty easy to do this as I just had to decompile the Microsoft rule AssembliesHaveVersionNumbers in DesignRules.DLL. A quick peek at the rule showed that the AssemblyNode passed to the Check method had a Version property on it. My hope was that there were either properties to get the attributes I wanted or a simple way to get attributes for an assembly. Of course, life wasn't so easy, but it turned out to be a good way to learn about the Introspection engine usage.

In a previous Reflector exploration mission, I saw that the Microsoft.Tools.FxCop.Sdk.Introspection.RuleUtilities class had several GetCustomAttributes methods that pointed out an Attributes property on the various Microsoft.Cci namespace types. Looking through the various rules files supplied with FxCop, you'll see that all the calls to any RuleUtilities.GetCustomAttribute method was using a set of predetermined attribute types defined in the Microsoft.Cci.SystemType class. As that particular GetCustomAttribute method takes a Microsoft.Cci.Module (a base class of Microsoft.Cci.AssemblyNode) and an attribute type, it looked like the one to use in my case. Therefore, my next mission was to find an example of getting a type out of a Module or AssemblyNode class to see what method to call.

I thought I might have to do a lot of searching with Reflector, but the RuleUtilities class had what I needed so I didn't have to look far. There is a public field called WebMethodAttribute that looked to be a custom attribute lookup. Looking through the RuleUtilities.GlobalBeforeAnalysis method, I saw towards the bottom of the method that it called the AssemblyNode.GetType property, passing in the Microsoft.Cci.Identifier classes for the namespace and the type. That was the hint I needed because in my spelunking of the Microsoft.Cci.SystemTypes class, the SystemAssemblies property returns the MSCORLIB.DLL AssemblyNode.

The Identifier class is, as the name implies, the Introspection identifier for an element (a TypeNode in Introspection terms). In almost all cases, you can get the identifier for a particular TypeNode either by passing the string name of the identifier you want to the Identifier constructor or by using the static Identifier.For method. If you happened to follow along with Reflector for the last few paragraphs, you saw that the description is far more complex than the actual gyrations I went through.

To tie everything together, Figure 9 shows the Check method that implements the actual rule and the AttributeType method to get the type. Since all the assembly attributes rules I wrote (AssembliesHaveCompanyAttributesRule, AssembliesHaveCopyrightAttributesRule, AssembliesHaveDescriptionAttributesRule, and AssembliesHaveTitleAttributesRule) are almost identical they all share a base class, BaseAssemblyAttributeRule, and simply define the abstract AttributeType method to return the particular method to look up. You can see all the action in full in the Bugslayer.FxCop.DesignRules project.

Figure 9 Checking if Assembly Attributes Exist

public override TypeNode AttributeType ( ) { if ( null == typeCopyrightAttribute ) { typeCopyrightAttribute = SystemTypes.SystemAssembly.GetType ( Identifier.For("System.Reflection"), Identifier.For("AssemblyCopyrightAttribute")); Debug.Assert ( null != typeCopyrightAttribute, "null != typeCopyrightAttribute" ) ; } return ( typeCopyrightAttribute ) ; } public override Problem[] Check ( AssemblyNode theAssembly ) { // Assume everything will work. Problem [] retvalue = null ; // Get the custom attribute type. AttributeList attribList = RuleUtilities.GetCustomAttributes ( theAssembly, AttributeType ( ) ) ; if ( 1 == attribList.Length ) { // Get the value of the attribute since it does exist. String strValue = attribList[0].Expressions[0].ToString() ; if ( 0 == strValue.Length ) { // The attribute is there but has an empty value. retvalue=Problems.AsArray(GetResolution(theAssembly.Name)); } } else { // The attribute is missing. retvalue = Problems.AsArray (GetResolution (theAssembly.Name)); } return ( retvalue ) ; }

Before I end the column, I want to point out a few things that'll help you develop your rules. The first should be obvious—you definitely need to take a long look at the Microsoft.Tools.FxCop.Sdk.Introspection.RuleUtilities class in Reflector so you can start learning how to utilize the Microsoft.Cci classes in your rules. The second item is that while there is a logging and tracing system built into FxCop, debugging your code through print statements is passé. To debug your rules, set the Debug Mode to Program and the Program field (both in the Debugging property page) to FXCOP.EXE and you'll debug your rule with ease.

Wrap-up

Now when the bad code comes for you, you've got FxCop to fight back. I hope you find the rules and NoPartialAnalysis I presented here useful and that they will help in your development efforts. I strongly encourage you take a look at the code and spend some time with Reflector looking at the Introspection engine rules in FxCop so you can see how all the magic is done. If you're looking for a rule to learn about writing rules, here are two ideas:

  • A rule to read and process the Deprecated and Inappropriate sections in CustomDictionary.XML. Take a look at the MemberNamesShouldBeSpelledCorrectly rule in UsageRules.DLL for some hints on how to start.
  • A rule that checks the spelling in embedded resource files.

If you're curious about what's coming up in my future advanced rules discussion, I've already written a couple of cool rules. The first shows how to do configurable rules and checks to see if you have too much boxing and unboxing going on in any method. I've also created a rule that checks the exceptions thrown directly from a method and ensures you've documented them with <exception> tabs in the XML documentation comments for that method. I've also got some other interesting rules in the works that I'll refine before my next installment.

Tips

Tip 63 At this point, I've only seen one other person who's posted rules on the Web, Anand M, at https://www.microsoft.com/en-us/download/details.aspx?id=6544. His rules are based on the older Reflection engine, but do show you how to write those rules in Visual Basic .NET.

Tip 64 If you're starting to do the very cool Tablet PC development and need another test machine, but can't afford a second Tablet PC, I ran across a great idea on how to mimic one at Can't Afford a Tablet PC? Try This!. Wei-Meng Lee and Brian Jepson point out that you can install Windows Tablet PC Edition on a laptop and hook up a graphics tablet. That gives you an instant test machine for about $100!

Send your questions and comments for John to  slayer@microsoft.com.

John Robbins is a cofounder of Wintellect, a software consulting, education, and development firm that specializes in programming for .NET and Windows. His latest book is Debugging Applications for Microsoft .NET and Microsoft Windows (Microsoft Press, 2003). You can contact John at https://www.wintellect.com.