Tuesday, July 3, 2007

Creating FxCop rules with F#

In this post I'm going to show a little example of an FxCop rule created with F#. My goal is to show that F# features can be used to define code rules.

The purpose of the post is not to show how to create FxCop rules, a nice explanation can be found in this useful blog entry: FxCop This (also useful links are provided).

The rule that I'm going to show is used to detect assignments from a field to itself. For example:


public class AClass {

int aValue;
public void AMethod(int aValeu){
this.aValue = aValue;
...
}
...
}


In this example, an error typing the name of the first argument of AMethod leads to an incorrect assignment of field aValue to itself. The C# compiler gives you a warning on this, however the code is generated because it is a valid program.

In order to create an FxCop rule that detects this, we have to identify the IL code sequence that represents the assignment. By using ILDASM we can see this:


.method public hidebysig instance void AMethod(int32 aValeu) cil managed
{
// Code size 14 (0xe)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: ldfld int32 AClass::aValue
IL_0008: stfld int32 AClass::aValue
...
} // end of method AClass::AMethod



So what we need to find is the simple pattern of a LDFLD opcode followed by a STFLD to the same field. The code for this rule looks like this:


type SameFieldAssignment = class
inherit BaseIntrospectionRule
new() = {inherit BaseIntrospectionRule("SameFieldAssignment","SameFieldAssignment",Assembly.GetExecutingAssembly());}

override x.Check(m : Member) =
match m with
| (:? Method as meth) ->
let instrs = instructions_to_list meth.Instructions
in
x.find_assignment instrs
| _ -> x.Problems

member x.find_assignment (instructions : Instruction list) =
match instructions with
| (load::store::r) when
(load.OpCode = OpCode.Ldfld &&
store.OpCode = OpCode.Stfld &&
load.Value = store.Value)
-> x.Problems.Add(new Problem(x.GetNamedResolution("SameFieldAssignment",[||]),store.SourceContext))
x.find_assignment r
| (_::r) -> x.find_assignment r
| _ -> x.Problems
end


The find_assignment method is used to search for the desired pattern. The IL code sequence is converted to a F# list (via the instructions_to_list utility function), then a list pattern is the used to identify the desired pattern.

We can improve this code by using active patterns. For example we can define the following patterns:


let (|Ldfld|_|) (i : Instruction) =
if (i.OpCode = OpCode.Ldfld) then
Some ((i.Value :?> Field) , i.SourceContext)
else
None

let (|Stfld|_|) (i : Instruction) =
if (i.OpCode = OpCode.Stfld) then
Some((i.Value :?> Field),i.SourceContext)
else
None

let (|MethodContent|_|) (m : Member) =
match m with
| (:? Method as meth) ->
Some (meth.FullName,
instructions_to_list meth.Instructions)
| _ -> None


Now we can write:


type SameFieldAssignmentAp = class
inherit BaseIntrospectionRule
new() = {inherit BaseIntrospectionRule("SameFieldAssignmentAp","SameFieldAssignmentAp",Assembly.GetExecutingAssembly());}

override x.Check(m : Member) =
match m with
| MethodContent(_,instrs) -> x.find_assignment instrs
| _ -> x.Problems

member x.find_assignment (instructions : Instruction list) =
match instructions with
| (Ldfld(lField,_)::Stfld(sField,source)::r) when (lField = sField)
-> let p = new Problem(x.GetNamedResolution("SameFieldAssignmentAp",[|lField.FullName|]),source) in
x.Problems.Add(p)
x.find_assignment r
| (_::r) -> x.find_assignment r
| _ -> x.Problems
end


Code for this experiment can be found here.