Tim Weaver

A .NET Blog

<November 2008>
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456


Navigation

Work

Subscriptions

Post Categories



FXCop and the Introspection Engine in VS2005

One of the new features of VS2005 will be static code analysis ala FXCop. I’ve been spending a lot of time working with the Introspection engine in the latest version of FXCop and I must say that it is really powerful. The amount of information you can gain from an assembly that is being processed is staggering.
Just to make sure it was still there I searched the Beta 1 refresh and sure enough the introspection engine was installed as part of the static code tool:
C:\Program Files\Microsoft Visual Studio 8\Enterprise Developer Tools\Static Analysis Tools\FxCop\Engines

John Robbins posted a couple of good bugslayer articles on MSDN talking about the Introspection engine and how to use it. I started with his samples and created a BaseEmptyFXCopRule class:

    9     [CLSCompliant(false)]

   10     abstract public class BaseEmptyFxCopRule : BaseIntrospectionRule

   11     {

   12         protected  BaseEmptyFxCopRule ( String name )

   13             : base ( name                                ,

   14                      "Monster.FXCop.Rules.RuleData"           ,

   15                      typeof(BaseEmptyFxCopRule).Assembly  )

   16         {

   17         }

   18     }

The rule is defined via an Xml file:
<Rules>
  <Rule TypeName="CookieRule" Category="Monster.Test" CheckId="CK111">
    <Name>CookieRule -- prevent unauthorized cookie values</Name>
    <Description>Don't allow arbitrary usage of cookies</Description>
    <LongDescription>Test</LongDescription>
    <FixCategories>NonBreaking</FixCategories>
    <Owner>T Weaver</Owner>
    <Url>some URL</Url>
    <Resolution>Contact your Team Lead to get approval to add the cookie value</Resolution>
    <Email>icodemarine@gmail.com</Email>
    <MessageLevel Certainty="99">Warning</MessageLevel>
  </Rule>
</Rules>

Note that the XML file has to be part of the project and its BuildAction property must be set to Embedded Resource.

So now I have a base rule and some textual data to back it up. The idea behind this rule was that we wanted to limit the number of cookies that are added by developers. At first thought this seemed like a fairly simple task. We will use the introspection engine to search for new objects of type HttpCookie. From there we will get the name assigned to the cookie and compare to some known good list to determine if we have a problem.

That’s the ideal scenario. The reality is rather different. First off there are a number of ways to add a cookie and not all result in an explicit call to create a new HttpCookie object.

For example:

  126         private void SetCookieValue8()

  127         {

  128             HttpCookie cookie = new HttpCookie("testCookie");

  129             cookie.Value="mytest";

  130  

  131        

Results in the following IL:
.method private hidebysig instance void SetCookieValue8() cil managed
{
// Code size 23 (0x17)
.maxstack 2
.locals init ([0] class [System.Web]System.Web.HttpCookie cookie)
IL_0000: ldstr "testCookie"
IL_0005: newobj instance void [System.Web]System.Web.HttpCookie::.ctor(string)
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: ldstr "mytest"
IL_0011: callvirt instance void [System.Web]System.Web.HttpCookie::set_Value(string)
IL_0016: ret
} // end of method FXCopRulesTest::SetCookieValue8

As expected there is a newobj @ IL0_0005. From there we “know” that the 1st parameter above the ctor will be the name. So we can search for ldstr and get “testCookie”.

Now look at this method (_cookieName is a class level variable):

  122         private void SetCookieValue7(string cookieValue)

  123         {

  124             HttpContext.Current.Response.Cookies[_cookieName].Value =cookieValue;

  125         }

Here’s the IL:
.method private hidebysig instance void SetCookieValue7(string cookieValue) cil managed
{
// Code size 33 (0x21)
.maxstack 3
IL_0000: call class [System.Web]System.Web.HttpContext [System.Web]System.Web.HttpContext::get_Current()
IL_0005: callvirt instance class [System.Web]System.Web.HttpResponse [System.Web]System.Web.HttpContext::get_Response()
IL_000a: callvirt instance class [System.Web]System.Web.HttpCookieCollection [System.Web]System.Web.HttpResponse::get_Cookies()
IL_000f: ldarg.0
IL_0010: ldfld string FXCopTest.FXCopRulesTest::_cookieName
IL_0015: callvirt instance class [System.Web]System.Web.HttpCookie [System.Web]System.Web.HttpCookieCollection::get_Item(string)
IL_001a: ldarg.1
IL_001b: callvirt instance void [System.Web]System.Web.HttpCookie::set_Value(string)
IL_0020: ret
} // end of method FXCopRulesTest::SetCookieValue7

Notice there isn’t any explicit newobj for HttpCookie. So we can’t just search for that we have to check for a callvirt against a type of HttpCookie with a name of set_Value. If that weren’t complex enough there is the overloads to think of so we also have callvirt against type of HttpCookie with a name of set_Item.

Okay so at this point we think we have the possible values to search for. Now, assuming we can find each place where a cookie is being created or set how do we get the name of the parameter? There are a number of ways but none of them are 100%. This is static code analysis and the actual name of the cookie could be coming from a DB, Config File or some other source not available at build time. So lets assume we can accept that, how do we get all the others?

Those variables that are declared within the method are fairly simple to get. You just find the call to get_Item(string) and move up the call stack until you find the parameter that matches that string. In this case the parameter is ldfld, which means our value isn’t declared in this method it is a property on the class. Notice that ldarg.0 is for the value assigned to the cookie, not it’s name. So now we have yet another hurdle. The values we want may be params to the method that uses the cookie or fields, etc.

I decided that if I can get 80% of the instances that would serve as a solid foundation. If all I can do is identify each place where a cookie is being set that will help a lot. Of course a simple grep of the code base would give you this with a lot less work—not as much fun, but less work.

I’m not going to post the entire rule because a) it doesn’t work yet and b) its too large. However here is the overloaded Check() method that is called by the Introspection engine. You can see how I had to work through the various possibilities to ensure I got all the various ways a cookie could be created.

   26         #region Check

   27         /// <summary>

   28         /// Called by FXCop

   29         /// </summary>

   30         /// <param name="member"></param>

   31         /// <returns></returns>

   32         public override ProblemCollection Check( CCI.Member member)

   33         {

   34             if(_problems==null)

   35             {

   36                 _problems = new ProblemCollection(this);

   37             }

   38             CCI.Method method = member as CCI.Method;

   39             if(method ==null)

   40                 return null;

   41  

   42             //get the instructions for this method

   43             CCI.InstructionList instructions = method.Instructions;

   44             if(0 == instructions.Length)

   45                 return null; // not likely but just in case we don't want to throw an error

   46  

   47             //loop through our instructions and see if we can find any items of interest

   48             for(int i=1; i<instructions.Length;i++)

   49             {

   50                 CCI.Instruction instr = instructions[i]; //get the current instruction

   51                 //if we are creating a new object check for a HttpCookie

   52                 if(CCI.OpCode.Newobj ==instructions[i].OpCode)

   53                 {

   54                     if(IsTypeHttpCookie(instructions[i], true))

   55                     {

   56                         FindCookieValue(instructions, method, i);                       

   57                     }

   58                 }

   59                 else //okay we do it the hard way. Look at opCodes for virtual calls that match our signature

   60                 {

   61                     if(CCI.OpCode.Callvirt ==instructions[i].OpCode )

   62                     {                   

   63                         if(IsTypeHttpCookie(instructions[i], false))

   64                         {

   65                             //virtual call so we need to get the params

   66                             CCI.Method mthd= instr.Value as CCI.Method;

   67                             if(mthd!=null)

   68                             {

   69                                 switch(mthd.Name.Name)

   70                                 {

   71                                     case "set_Value":

   72                                     case "set_Item":

   73                                         FindCookieValue(instructions, method, i);

   74                                         break;

   75                                 }

   76                             }//method !=null

   77                         }//cookietype

   78                     } //callvirt

   79  

   80                 }//not a new object

   81             }

   82             return _problems;

   83         }

   84         #endregion

FXCop can be a powerful tool in your arsenal to help ensure you get code that not only meets the best practices “standards” that Microsoft has put out, but also any custom standards you may have. It takes a little work to build a rule and the Introspection engine isn’t documented yet so you will have to spend a lot of time with Reflector, but ultimately the payout is worth it.

posted on Wednesday, November 24, 2004 7:41 AM by icodemarine





Powered by Dot Net Junkies, by Telligent Systems