Wednesday, July 14, 2010

A custom FxCop rule - calling Debug methods

While working on a recent production issue, we ran into an interesting problem. We suspected an exception was being thrown when calling an external site, but we couldn't prove it. Our usual method of exception handling is to save the details to a rolling log file. This particular service, however, was failing without leaving behind any info as to why. Digging through the code revealed a catch block similar to this:

  catch (Exception ex)
  {
      Debug.WriteLine(ex.ToString());
  }

For now, ignore the fact that you will only see output using something like DebugView. The real problem is that when the code is compiled in Release mode, calls to Debug.WriteLine are removed completely. So in the example above, the catch block will be empty.

To reduce the odds of this sort of thing happening in the future, I decided to write a custom FxCop rule to locate any calls to methods that had been tagged with a "Debug" conditional. It wasn't as easy as I had expected, but there is an excellent tutorial on the subject. The only difficulty I had was in locating the ConditionalSymbol property in the class tree.

For anyone interested, here is the xml rule file:


    <?xml version="1.0" encoding="utf-8" ?>
    <Rules FriendlyName="Custom Rules">
        <Rule TypeName="DoNotCallDebugConditionalMethods" Category="CustomRules" CheckId="PG1001">
            <Name>Do not call debug conditional methods</Name>
            <Description>Calls to methods marked with the DEBUG conditional will be removed
            from Release builds.</Description>
            <Url></Url>
            <Resolution>Replace the call to '{0}' with a more appropriate call</Resolution>
            <MessageLevel Certainty="100">Warning</MessageLevel>
            <Email></Email>
            <FixCategories>DependsOnFix</FixCategories>
            <Owner></Owner>
        </Rule>
    </Rules>

And the code:


  namespace CustomFxCopRules
  {
      /// <summary>
      /// Warns of any methods being called that are removed from non-debug builds,
      /// such as Debug.WriteLine()
      /// </summary>
      public class DoNotCallDebugConditionalMethods : BaseIntrospectionRule
      {
          public DoNotCallDebugConditionalMethods()
              : base("DoNotCallDebugConditionalMethods", "CustomFxCopRules.rules.xml",
                  typeof(DoNotCallDebugConditionalMethods).Assembly)
          { }
 
          public override ProblemCollection Check(Member member)
          {
              Method method = member as Method;
              if (method != null)
              {
                  VisitStatements(method.Body.Statements);
              }
 
              return Problems;
          }
 
          public override void VisitMethodCall(MethodCall call)
          {
              var member = ((MemberBinding)call.Callee).BoundMember;
              var method = (Method)member;
              var symbol = method.ConditionalSymbol;
 
              if (!string.IsNullOrEmpty(symbol) && symbol.Contains("DEBUG"))
              {
                  Problems.Add(new Problem(GetResolution(method.FullName), call.SourceContext));
              }
          }
      }
  }