I've been reading lately about PreFast, a tool for detecting fault in applications based on static analyis. Sadly, PreFact is for C++, so I wondered: can we do the same in C# ?
The test fault:
So I started with simple fault: a method that uses nullable argument and that does not check for nullity -> the NullReferenceException fault (NRE). Here are three method that illustrate the problem:
public class MyClass { public void ArgumentNotChecked(Object arg) { string s=arg.ToString(); // if arg is null, NRE } public void ArgumentChecked(Object arg) { if (arg==null) throw new ArgumentNullException("arg"); string s=arg.ToString(); // ok, we know arg is not null } public void ArgumentGuarded(Object arg) { if (arg!=null) { string s=arg.ToString(); // ok, we know arg is not null } } }
Sketch of the solution
The solution (I found) to this problem can be understood as taking all the possible path in the IL graph, while keeping track of the current state of the argument: Null, NonNull or Uncertain. When starting, the argument is uncertain, it could be null or not. If a method instance of the argument is called 3 things can happen:
In terms of graph theory (and QuickGraph), we need to apply the EdgeDepthFirstSearch algorithm using a visitior (IEdgeColorizerVisitor) that the job described above. More that words, let's explain this on schemas.
EdgeDepthFirstSearch
IEdgeColorizerVisitor
Checking the ArgumentNotChecked method
This methods is very simply. In IL, it looks like this:
ldarg.1 call Object.ToString()
The corresponding graph is the following (ColorCode: Orange=Uncertain, Red=Null, Green=NonNull)
The EdgeDepthFirstSearch has only one step: it explorezs the ldarg.1 -> ToString() edge. Since the ldarg.1 status when calling ToString is Uncertain, it should output a warning. So in Reflector I get:
Gotcha!
Checking the ArgumentChecked method
This method is already a bit more complicated since there is a branch. The IL is:
L_0000: ldarg.1 L_0001: brtrue.s L_000e L_0003: ldstr "arg" L_0008: newobj instance void [mscorlib]System.ArgumentNullException::.ctor(string) L_000d: throw L_000e: ldarg.1 L_000f: callvirt instance string object::ToString() L_0014: stloc.0 L_0015: ret
and the IL graph looks like this:
The EdgeDepthFirstSearchAlgorithm starts by examining the ldargs.1 -> brtrue.s edge.
If we find a brtrue.s, we can easily check if it is applied to ldarg1. If yes, then we should update the argument state in the child edges. The "then" branch will be tagged Null, the "else" branch will be tagged NonNull as depicted in the figure below:
Now, when we encounter the ToString() call on the argument, it's state is set to NonNull, so that's ok.
Now, in Reflector, the fault analysis on the method should not find any warnings:
It worked!
Conclusion
Fault analysis can be seen as a natural application of graph theory and QuickGraph is a good for doing this. Although Reflector is a great tool, I think fault detection needs to be implemented as FxCop rules.... so stay tuned for new adventures.
Page rendered at Thursday, December 04, 2008 6:27:49 AM UTC
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.