ReSharper: Analysis on NullReferenceException and contracts for it

    If you use ReSharper, then you are probably familiar with its highlighting “Possible 'NullReferenceException'” . In this article, I will briefly talk about the analyzer that generates warnings of this kind, and how to help it do it better.

    Immediately consider an example:

    public string Bar(bool condition)
    {
      string iAmNullSometimes = condition ? "Not null value" : null;
      return iAmNullSometimes.ToUpper();
    }

    * This source code was highlighted with Source Code Highlighter.

    ReSharper rightly highlights iAmNullSometimes in the second line of the method with this warning. Now select the method:

    public string Bar(bool condition)
    {
      string iAmNullSometimes = GetNullWhenFalse(condition);
      return iAmNullSometimes.ToUpper();
    }

    public string GetNullWhenFalse(bool condition)
    {
      return condition ? "Not null value" : null;
    }

    * This source code was highlighted with Source Code Highlighter.

    After this operation, the warning disappears. Why it happens?


    Analyzer


    The analyzer tries to identify what values ​​the used variables can have. I’ll clarify to what level of abstraction knowledge about the value of a variable is reduced. From the point of view of the analyzer, a variable can have one or several states:

    * NULL , NOT_NULL - indicates that the link has a zero or nonzero value;
    * TRUE , FALSE - similarly for type bool ;
    * UNKNOWN - value entered for optimistic analysis, with the help of which the number of false positives is reduced.

    As a result of the analyzer, for each point of use of variables, a set of possible states is determined.

    In the first listingiAmNullSometimes after initialization will have two possible states: NULL and NOT_NULL . Therefore, the “Possible NullReferenceException” highlighting tells us that there is at least one program execution path in which iAmNullSometimes will be null (in this case, the path in which condition is false).

    The second case is more complicated. The analyzer does not know what values GetNullWhenFalse returns . Of course, you can analyze it and make sure that it can return null. But with an increase in the number of methods that also cause something, the time spent on such an analysis does not allow the analyzer to be used on the fly on modern PCs (so that ReSharper can set the highlighting of possible errors). Moreover, the called method may end up in the library referenced by our project. We will not decompile and analyze it in the same way on the fly.

    There is one more option. Assume that external methods of which nothing is known return either NULL or NOT_NULL . This is how pessimistic analysis works.

    ReSharper uses optimistic analysis by default. In it, if nothing is known about the method, then the return value will be in a special state UNKNOWN. A variable that is in this state at the time of its use is not highlighted (unless, of course, there are other paths in which it was assigned null explicitly or from the CanBeNull method). In the second listing, this makes the analyzer “lose vigilance”.

    The analyzer and its operating modes require a separate article, so I will write about them separately.

    In the case of both optimistic and pessimistic analysis, I still want to somehow know what the called method is capable of, so that ReSharper finds more potential errors. Here we come to the aid of contracts.

    Contracts


    The ReSharper analyzer can use additional knowledge about the called methods, getting it through contracts like “a method never returns null ”, “a method can return null ”, “you cannot substitute null into a parameter ”. In the simplest case, these contracts are defined using the JetBrains.Annotations.CanBeNullAttribute and JetBrains.Annotations.NotNullAttribute attributes . Applying an attribute to a method will indicate whether it can return null . To the parameter - on the permissibility of substituting a zero value. They can also be applied to properties and fields. These attributes are defined in the JetBrains.Annotations.dll library , which lies in\ Bin.

    The example given in the second listing can be improved marking method GetNullWhenFalse attribute CanBeNull :

    public string Bar(bool condition)
    {
      string iAmNullSometimes = GetNullWhenFalse(condition);
      return iAmNullSometimes.ToUpper();
    }

    [CanBeNull]
    public string GetNullWhenFalse(bool condition)
    {
      return condition ? "Not null value" : null;
    }

    * This source code was highlighted with Source Code Highlighter.

    When using the iAmNullSometimes variable method, in this case the highlight “Possible 'NullReferenceException'” appears .

    If you don’t want in your project to drag along an additional assembly, which also does not add functionality in runtime, then you can declare these attributes directly in your project. The analyzer is suitable for using any attributes from any assemblies, if only their names coincide with those specified in JetBrains.Annotations.dll . The definitions of these attributes can be easily obtained using the Copy default implementation to clipboard button located on one of the ReSharper's configuration pages:



    External annotations


    If you want to use an external library (eg. Mscorlib.dll ), registering contracts for its entities using attributes is not possible. External Annotations come to the rescue here. This ReSharper feature allows you to complement already compiled entities with attributes used by the ReSharper analyzer. External Annotations provide an opportunity to “trick” the analyzer - to make it so that it sees attributes, parameters, and other declarations that were not declared when the library was compiled. To do this, the attributes must be written in the XML file located in the directory\ Bin \ ExternalAnnotations.

    This defines the contracts for standard libraries that get into this folder when installing ReSharper. These contracts were derived from source code analysis and Microsoft Contracts. The contracts obtained as a result of the first approach are located in files with the names * .Generated.xml , as a result of the second, in * .Contracts.xml .

    Files describing additional attributes have a structure similar to the structure of XmlDoc files. For example, for the XmlReader.Create (Stream input) method from the System.Xml assembly of the fourth framework, NotNull contracts are defined as follows:


     
      
      
       
      

     



    * This source code was highlighted with Source Code Highlighter.

    For ReSharper to pick up the file, you need to place it in one of the following ways: \ Bin \ ExternalAnnotations \.xml or \ Bin \ ExternalAnnotations \\.xmlwhere - assembly name without specifying a version. If you arrange files in the second way, then for one assembly you can specify several sets of contracts. This may be necessary to distinguish assembly contracts with different versions.

    Now editing these files is not very convenient and involves a lot of manual work, which also requires administrator rights. But in the near future it is planned to release a tool that simplifies this work. Most likely it will be issued as a plug-in to ReSharper.

    Application


    Several practices of using External Annotations that improve life when working with ReSharper.

    XmlDocument.SelectNodes (string xpath)


    Abstract CanBeNull for this method is often a topic of bug reports. The fact is that SelectNodes is a method of the XmlNode class and in general can return null (for example, for XmlDeclaration ). But most often we use this method when it never returns null , - from XmlDocument . One solution might be to remove the corresponding annotation from External Annotations or replace it with NotNull . But you can do the right thing by writing an extension method for XmlDocument :

    public static class XmlUtil
    {
      [NotNull]
      public static XmlNodeList SelectNodesEx([NotNull] this XmlDocument xmlDocument, [NotNull] string xpath)
      {
        // ReSharper disable AssignNullToNotNullAttribute
        return xmlDocument.SelectNodes(xpath);
        // ReSharper restore AssignNullToNotNullAttribute
      }
    }

    * This source code was highlighted with Source Code Highlighter.

    In this case, of course, it would be nice to make methods like SelectElements and SelectAttributes to avoid type conversion every time, but that's another story.

    Assertion


    If you use your own (or third-party) Assert methods in your project , you can mark them with the attributes AssertionMethodAttribute and AssertionConditionAttribute . Direct candidates for this tagging are the Contracts.Assert methods , if you use Microsoft Contracts:


     
      
      
       
        0
       

      

     

     
      
      
       
        0
       

      

     



    * This source code was highlighted with Source Code Highlighter.

    You can also look towards TerminatesProgramAttribute if you have methods that always throw an exception.

    And lastly


    One article does not fit all. I plan to write some more articles about the analyzer and its use. Which way my story will go depends on what will be interesting to the habrasociety: optimistic and pessimistic analyzers, how to programmatically get annotations from source codes or something else.

    And yes. Annotate your code with contracts and you will be welcome!

    Also popular now: