Creating Code Analysis Rules


Team System includes many code analysis rules, but no matter how comprehensive the rules from Microsoft are, they can never fully cover the specific requirements of your own projects. Perhaps you have specific naming conventions or a standard way to load database connection strings. In many cases, you can create a custom code analysis rule to help diagnose the issue and help developers take corrective action.

Reflection and Introspection

Many static analysis tools use simple source-code inspection to identify issues. However, with FxCop, Microsoft decided to leverage the inherent functionality of .NET itself as the basis for creating rules. A very useful feature of .NET is called reflection. Using reflection, you can programmatically inspect other assemblies, classes, and members. You can even invoke methods or access fields, public or private, given appropriate security settings. Reflection is done without establishing a link to the target assembly at compilation time, a practice known as late binding.

Initial versions of FxCop relied on reflection as the basis for rules. However, a newer option is available, called introspection. Similar to reflection, introspection can inspect a target assembly to discover its types, and details about those types. It can also invoke members of those types. Introspection does this in a much faster manner than reflection and supports multi-threaded operations. Furthermore, introspection does not lock the files under analysis, a problem suffered by previous versions of FxCop that needed to use reflection. Given the clear advantages of introspection over reflection, Microsoft has leveraged introspection with the rules that are shipped with Team System. We'll also use introspection in this section for our custom rule.

Creating a new rule

Creating a new rule can be challenging, so we will walk through the creation of one in this section. We'll continue working with the SampleLibrary created earlier in this chapter. You'll recall that when you ran code analysis on the SampleLibrary, a number of potential issues were flagged. There is actually another problem with the code that is not detected by the set of rules included with Team System.

In this section, we'll create a fairly simple rule to help correct a potentially serious issue. Exposing constant values from an assembly is a normal and expected practice, but with .NET there is a surprising side effect. When a second assembly references a source assembly that exposes a constant value, the value of that constant is actually stored directly in the IL of the referencing assembly. This means that even when you change and recompile the original assembly, the value is not changed in the referencing assembly. This can lead to extremely difficult-to-diagnose problems, and will require you to recompile all referencing assemblies even though those assemblies have not changed.

To address this, we'll create a new rule, AvoidExposingPublicConstants, which searches a target assembly for publicly visible constant values. Begin by creating a new C# Class Library project named "CustomCodeAnalysisRules."

Code Analysis loads designated assemblies and searches them for rule classes. Code Analysis rules implement a core interface called IRule. Rules that use introspection also implement IIntrospectionRule. However, these and other related interfaces and classes are wrapped by the helpful BaseIntrospectionRule class. We'll use this class as the basis for our own rules. To use this base class, add a reference in the CustomCodeAnalysisRules project to the FxCopSdk.dll and Microsoft.Cci.dll assemblies found in the \Team Tools\Static Analysis Tools\FxCop directory.

Creating a base rule

As mentioned before, most of the included code analysis rules inherit, typically indirectly, from a base class called BaseIntrospectionRule. While each custom rule could inherit directly from this class, it's easier to create a common base class that inherits from BaseIntrospectionRule. This is because the constructor to BaseIntrospectionRule requires three arguments. The first argument is the name of the rule, and the second is the name of the XML file containing rule data. The final argument is a reference to the rule assembly type.

If you created a rule assembly with multiple rules, each rule would have to supply those three arguments each time. However, with a new base class, you can abstract away the last two arguments and keep your rule code streamlined.

Create a new file called BaseStaticAnalysisRule.cs and add the following code:

      using System;      using Microsoft.FxCop.Sdk.Introspection;      namespace CustomCodeAnalysisRules      {          public abstract class BaseStaticAnalysisRule : BaseIntrospectionRule          {              protected BaseStaticAnalysisRule(string name) :                 base(name,                      "CustomCodeAnalysisRules.Rules",                      typeof(BaseStaticAnalysisRule).Assembly ) { }          }      }

Because the values of the second and third parameter to the BaseIntrospectionRule constructor will be the same for all rules in your assembly, you use this simple class as a wrapper in which those values can be set. The second argument, CustomCodeAnalysisRules.Rules, needs further explanation and is described in the section "Creating Rules.XML."

Implementing the rule

Now that you have a base class to use for all of your custom rules, you can create a rule. A rule has two main components:

  • Rule implementation code: This is the code that analyzes the target assembly and determines whether the standard or guideline it is trying to enforce has been violated.

  • Rule descriptive XML: Having the implementation code is not enough. An embedded XML fragment is required in order to help Managed Code Analysis display the rule and provide details such as descriptions and resolutions to the user.

Before you can create the rule's implementation, you need to have an approach for evaluating and inspecting a target assembly. While there are many ways you could write code to do this, Microsoft has made the job much easier by including the Microsoft.Cci assembly with Team System. You'll learn what this assembly is and how to use it in the following section.

Using the Microsoft.Cci assembly

The Microsoft.Cci assembly, or Common Compiler Infrastructure, originated from Microsoft Research and contains classes that provide features for language-based tools, such as compilers. This assembly is especially helpful for code analysis because it offers many classes that map directly to common programming constructs such as classes, fields, members, and methods. You'll use these classes to inspect target assemblies to identify the places where your rule applies.

Note

You may be familiar with the System.CodeDom namespace. It is very useful for creating intermediate representations of programming constructs and then using a language provider to generate code in a desired language, such as VB.NET. Microsoft.Cci, conversely, offers additional features for reading and inspecting existing code, exactly the task we face when creating a code analysis rule.

The following table lists the major classes offered by Microsoft.Cci, organized by programming concept.

Open table as spreadsheet

Programming Concept

Related microsoft.Cci Classes

Assembly

CompilationUnit

Namespace

Namespace

Types

Class, Struct, Interface

Type Member

Member

Member

Method, Field, Property, Event, EnumNode

Method

Method, InstanceInitializer, StaticInitializer

Statement

Block, AssignmentStatement, If, For, ForEach, DoWhile, While, Continue, ExpressionStatement, VariableDeclaration, Return, Switch, Lock

Expression

Variable, AssignmentExpression, UnaryExpression, BinaryExpression, NaryExpression, Literal, Parameter, Local

Exception-Related

ExceptionHandler, Throw, Try, Catch, Finally

Instructions and Operations

Instruction, OpCode

The members of the Microsoft.Cci namespace are organized in a hierarchical structure, with related classes organized under a parent type. For example, the Member class is the parent for the types of things you'd expect to have as class members, Method, Property, Field, Event, and others. If you have a Method instance, you can use its members to obtain references to the items it contains. For example, the Instructions property returns an InstructionList that you can use to loop through the operations of the method. Similarly, the Method class also has a Pamameters field, returning a ParameterList instance that can be used to inspect each parameter to the method.

To use the Microsoft.Cci assembly, add a reference to the Microsoft.Cci.dll assembly in the \Team Tools\Static Code Analysis\FxCop directory.

The IIntrospectionRule interface

As mentioned earlier, one of the abstractions our base class makes is the implementation of the IIntrospectionRule interface. This interface gives you a chance to specify the conditions under which you want your rule to be invoked. IIntrospectionRule contains the following members:

      ProblemCollection Check(Member member);      ProblemCollection Check(Module module);      ProblemCollection Check(Parameter parameter);      ProblemCollection Check(Resource resource);      ProblemCollection Check(TypeNode type);      ProblemCollection Check(string namespaceName, TypeNodeList types);

These overloads of the Check method give you a chance to indicate that your rule should be called when a specific kind of programming construct is currently the focus of the code analysis engine. You do not need to implement all of the Check methods in your custom rules, only the ones that expose the constructs you need.

In our example, we're looking for constants in an assembly, so we need to observe the various members of each class, looking for those that are constants and exposed publicly. Therefore, we need to use the Check(Member member) overload. This method will be called each time the analysis engine finds any type member, be it a constant, method, field, property, or other member type.

Writing the rule implementation code

You now have a base class for your rule and an understanding of the Microsoft.Cci namespace and IIntrospectionRule methods that will help you write the implementation. Create a new class file, AvoidExposingPublicConstants.cs. First, add using statements for the namespaces you'll use:

      using System;      using Microsoft.Cci;      using Microsoft.FxCop.Sdk;      using Microsoft.FxCop.Sdk.Introspection;

Now, create the class, inheriting from the BaseStaticAnalysisRule you created earlier:

      namespace CustomCodeAnalysisRules      {         public class AvoidExposingPublicConstants : BaseStaticAnalysisRule         {            public AvoidExposingPublicConstants() :                   base("AvoidExposingPublicConstants") {}            public override ProblemCollection Check(Member member)            {                Field f = member as Field;                if (f == null)                {                    //Not a field                    return null;                }                if (member.DeclaringType is Microsoft.Cci.EnumNode)                {                    //Inside an enumeration                    return null;                }                if (member.IsVisibleOutsideAssembly && f.IsLiteral)                {                    //Is publicly visible and is a constant                    Problems.Add(new Problem(GetResolution(member.Name.Name)));                }                return Problems;            }            public override TargetVisibilities TargetVisibility            {                get { return TargetVisibilities.ExternallyVisible; }            }         }     }

The constructor only has to supply the name of the rule to the base class constructor, which will forward the name of your XML data store and the assembly type reference automatically to the BaseIntrospectionRule.

As we determined before, we need to implement the Check(Member member) overload from the IIntrospectionRule and search each member for constants. The first thing we do is attempt to convert the Member to a Field instance. If this fails, we know the member was not a field and we can move on to the next member. If it is a Field, we check the Member to determine whether it was declared inside of an enumeration. We're not interested in enumerations, so we also return null in this case.

Finally, we verify that the member is publicly visible with the IsVisibleOutsideAssembly property, and that it is a constant, or literal, value with the IsLiteral property. If these expressions are true, you have a publicly visible constant, and your rule has been violated.

When a rule has been violated, you must create a new Problem instance and add it to your rule's Problems collection, provided by the BaseIntrospectionRule class. The argument to a Problem constructor is a Resolution instance. The BaseIntrospectionRule class offers a GetResolution helper method that loads the resource data from the embedded XML data for the current rule. Arguments to GetResolution are automatically inserted into any placeholders such as {0} and {1}in the rule's resolution text, in the same manner as String.Format.

The new Problem is added to the Problems collection and the Problems collection is returned, indicating to the Code Analysis tool that a new violation has been added.

The final item in the rule implementation is the TargetVisibility property. This property is used by the Code Analysis tool to determine when items should be fed into the rule's Check method(s). The TargetVisibilities enumeration has values such as All, ExternallyVisible, NotExternallyVisible, and Overridable that can be combined to indicate when the rule should be tested. In our case, we only care about publicly visible members, so we return TargetVisibilities.ExternallyVisible.

Creating Rules.XML

With the implementation written, you now need to create an XML node that describes the rule and provides text to help the user understand and address rule violations. The outer Rules node specifies the name of the group of rules — for example, Performance Rules. It contains one or more Rule nodes, each describing a single rule.

Add a new XML file to the project. Name the file Rules.xml and enter the following content:

     <?xml version="1.0" encoding="utf-8" ?>     <Rules FriendlyName="Custom Code Analysis Rules">       <Rule TypeName="AvoidExposingPublicConstants" Category="Wrox.Custom"        Check>         <Name>Avoid exposing public constants</Name>         <Description>The values of public constants are compiled into any referencing     assemblies. Should that value change, it is not sufficient to recompile the source     assembly because that value will also be stored in those referencing assemblies.     Avoid public constants for this reason.</Description>         <Resolution>Change public constant '{0}' to a readonly variable, or mark it as          private or internal.</Resolution>         <MessageLevel Certainty="99">Warning</MessageLevel>         <FixCategories>NonBreaking</FixCategories>         <Url>/Custom/AvoidExposingPublicConstants.html</Url>         <Email>yourname@yourcompany.com</Email>         <Owner>Contact Person's Name</Owner>       </Rule>     </Rules>

Important

You must embed the XML into the rule assembly, or the Code Analysis tool will not be able to load the XML and your rule will fail. Set this by right-clicking on the XML field and choosing Properties. Under the Advanced section, find the Build Action property and select Embedded Resource. When you build the assembly, the XML will be included in the meta-data.

The Rule node has a TypeName attribute, which should match the name of the rule class; a Category, which is used when displaying violations; and a CheckId, which uniquely identifies that rule — for example, in SuppressMessage attributes. Name is a short but friendly version of the rule name. Description contains the full description of what the rule is detecting.

Resolution is the full text shown to users to help them correct the violation. It may contain placeholders, such as {0}, which will automatically be replaced with values from the implementation code, as discussed in the previous section. This is extremely useful to help users quickly identify where problems exist. Resolution also supports an optional Name attribute, which enables you to specify multiple resolutions for the same rule, which can be selected at analysis time by your rule. To do so, instead of using the GetResolution method, use GetNamedResolution, supplying the name you wish to match.

MessageLevel provides a Certainty that the rule is applicable, with values from 0 to 99. A 99 indicates there is little doubt the rule has been violated and should be addressed. A lower value means violations of the rule are difficult to detect with great certainty. Use this value to indicate to the user how likely it is that a specific member or assembly has violated the rule. The element value of the MessageLevel can be any of the Microsoft.Tools.FxCop.Sdk.MessageLevel enumeration values, including Information,

Warning, CriticalWarning, Error, or CriticalError. Use these to indicate the relative severity of violating a rule. You can see this and the MessageLevel value in practice when you open the XML report from a Code Analysis run, as shown in Figure 8-4.

FixCategories indicate whether the changes needed to correct a rule violation should generally be considered breaking or nonbreaking. Values come from the Microsoft.Tools.FxCop.Sdk.FixCategories enumeration and can be Breaking, NonBreaking, or DependsOnFix. DependsOnFix ties back to the concept of multiple named resolutions. For example, a custom rule has two named resolutions, each used for different rule-violation scenarios. One resolution is easy to implement and considered nonbreaking, but the other is complex to correct, requiring a breaking change.

The Url is the path to an optional file that will show full details of the rule to the user, beyond what is given in the IDE. Email is the optional address of a contact person for help on the rule. Owner is the optionally provided name of a contact person for the rule.

Deploying a rule

You now have a complete rule assembly with embedded XML containing the supporting data for the contained rule(s). The easiest way to get Team System to use the contained rules is to move the assembly into the \Team Tools\Static Code Analysis\FxCop\Rules subdirectory of your Visual Studio installation directory. This will cause the IDE to recognize the rule assembly and read the contained rules so you can select them for inclusion.

However, you need to make a one-time change in order to get the Code Analysis engine to load custom rule assemblies at analysis time. MSBuild needs to be configured to load the custom rule assemblies so they can be applied. To do this, open the file Microsoft.CodeAnalysis.Targets in the C:\Program Files\MSBuild\Microsoft\VisualStudio\v8.0\Code Analysis directory. Find the CodeAnalysisRuleAssemblies node and replace it with the following line:

     <CodeAnalysisRuleAssemblies         Condition="'$(FxCopRuleAssemblies)'==''">$(FxCopDir)\rules     </CodeAnalysisRuleAssemblies>

Now, every time you run code analysis, any custom rule assemblies in the \FxCop\Rules\ directory will be loaded, and any enabled rules will be invoked.

A useful way to debug new rules is to create a single solution containing both the custom rule project and a sample target project with code that violates the rule. Open the Properties window for the rule assembly project and choose the Build Events tab. Add a post-build event command line to copy the rule assembly from the source project to the \Team Tools\Static Code Analysis\FxCop\Rules directory.

A problem with this approach is that if you open the Code Analysis properties window in either project, the custom rule assembly will be loaded and locked by the IDE. When this happens, you'll need to close and reopen Visual Studio. However, this approach will generally make your rule debugging process much easier.

Learning from existing rules

You've now seen how to create your own rules and integrate them into the Code Analysis tool of Team System. You will certainly find many uses for additional rules, but before you begin creating them you should invest some time learning from examples.

Our recommended approach for those wishing to implement custom rules is to look at how the rules that are included with Team System were written. While you don't have direct access to the source code for these rules, there is a tool that can help. "Reflector," written by Lutz Roeder and available at http://www.aisto.com/roeder/dotnet/, uses the power of .NET's reflection services to peer inside any assembly and generate an approximation of the source code. The target assembly can be any assembly, including those from Microsoft.

After obtaining Reflector, find the existing rules files in your Visual Studio 2005 installation directory under Team Tools\Static Analysis Tools\FxCop\Rules. Using Reflector, open one of the rule assemblies, such as PerformanceRules.dll. You can then navigate to the Microsoft.Tools.FxCop.Rules .Performance namespace, where you will see all of the rules in the Performance category.

Opening each rule will show you the details you've learned earlier in this chapter. The rules inherit from base helper classes, just as you saw with AvoidExposingPublicConstants and BaseStaticAnalysisRule. Opening the Resources node for any rule assembly enables you to view the XML data that was embedded in the assembly. Opening members such as the Check methods will show you code that, while not exactly original, will give you enough detail to determine how you might accomplish the same tasks in your own rules.



Professional Visual Studio 2005 Team System
Professional Visual Studio 2005 Team System (Programmer to Programmer)
ISBN: 0764584367
EAN: 2147483647
Year: N/A
Pages: 220

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net