If you use ReSharper, then you are probably familiar with its
“Possible 'NullReferenceException'” highlighting . In this article I will briefly tell you about the analyzer, which displays warnings of this kind, and how to help it do 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 such a 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 attempts to determine what values the variables used may have. I will specify to what level of abstraction knowledge about the value of a variable has been 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 non-zero value;
*
TRUE ,
FALSE - similarly for the
bool type;
*
UNKNOWN - the value entered for the optimistic analysis, which reduces the number of false positives.
As a result of the operation of the analyzer, for each point of use of variables a set of their possible states is determined.
In the first listing,
iAmNullSometimes 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 be in the library referenced by our project. We will not decompile and analyze it on the fly.
There is another option. Assume that external methods that are not known about anything 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 the special state
UNKNOWN . The variable that appears in this state at the time of its use is not highlighted (if, of course, there are no other ways on which
null was assigned to it explicitly or from the
CanBeNull method). In the second listing, this causes the analyzer to "lose vigilance."
The analyzer and its modes of operation require a separate article, so I will write about them separately.
As in the case of 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 contracts come to our aid.
Contracts
The ReSharper analyzer can use additional knowledge about the methods being called, obtaining it through contracts like “method never returns
null ”, “method can return
null ”, “you cannot substitute
null in the parameter”. In the simplest case, these contracts are specified using the attributes
JetBrains.Annotations.CanBeNullAttribute and
JetBrains.Annotations.NotNullAttribute . Applying an attribute to a method will indicate whether it can return
null . To the parameter - about the validity of the substitution of zero value. They can also be applied to properties and fields. These attributes are defined in the
JetBrains.Annotations.dll library, which lies in
<ReSharper install directory> \ Bin .
The example in the second listing can be improved by marking the
CanBeNull attribute with the
GetNullWhenFalse method:
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, the
“Possible 'NullReferenceException'” highlight appears in this case.
If you do not want in your project to take on an additional assembly, which also does not add functionality in runtime, then you can declare these attributes directly in your project. The analyzer will use any attributes from any assemblies, as long as their names match those specified in
JetBrains.Annotations.dll . The definitions of these attributes can be easily obtained using the
Copy default implementation button
to clipboard , located on one of the ReSharper settings pages:

External Annotations
If you want to use an external library (for example,
mscorlib.dll ),
assigning contracts to its entities using attributes is not possible. Here External Annotations come to the rescue. This ReSharper feature allows you to add already compiled entities with attributes used by the ReSharper analyzer. External Annotations provide an opportunity to "deceive" the analyzer - to make it so that it sees attributes, methods, and other declarations that were not declared when the library was compiled. To do this, you need to register the attributes in the XML file located in the
<ReSharper install directory> \ Bin \ ExternalAnnotations directory .
This defines contracts for standard libraries that fall 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 approach - 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, the
NotNull contracts are defined as follows:
< assembly name ="System.Xml, Version=4.0.0.0" > <!-- name , , -->
< member name ="M:System.Xml.XmlReader.Create(System.IO.Stream)" > <!-- , ; , XmlDoc- -->
< attribute ctor ="M:JetBrains.Annotations.NotNullAttribute.#ctor" /> <!-- XmlDoc- -->
< parameter name ="input" >
< attribute ctor ="M:JetBrains.Annotations.NotNullAttribute.#ctor" />
</ parameter >
</ member >
</ assembly >
* This source code was highlighted with Source Code Highlighter .
In order for ReSharper to pick up the file, it must be placed in one of the following ways:
<ReSharper install directory> \ Bin \ ExternalAnnotations \ <Assembly name> .xml or
<ReSharper install directory> \ Bin \ ExternalAnnotations \ <Assembly name> \ <Any name>. xml , where
<Assembly name> is the assembly name without specifying the version. If you place the files in the second way, then for a single assembly, you can specify multiple sets of contracts. This may be necessary to differentiate 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 we plan to publish a tool that simplifies this work. Most likely it will be designed as a ReSharper plugin.
Application
Several practices of External Annotations that improve life when working with ReSharper.
XmlDocument.SelectNodes (string xpath)
The
CanBeNull annotation for this method is often a topic for 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 an
XmlDocument . One solution would be to remove the corresponding annotation from External Annotations or replace it with
NotNull . But you can do it correctly by writing the extension method for the
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 still make methods like
SelectElements and
SelectAttributes , to avoid type conversion every time, but that's another story.
Assertion
If you use
Assert's own (or third-party) methods in your project, you can mark them with the attributes
AssertionMethodAttribute and
AssertionConditionAttribute . Direct candidates for this tagging are
Contracts.Assert methods, if you use Microsoft Contracts:
< assembly name ="Microsoft.Contracts" >
< member name ="M:System.Diagnostics.Contracts.Contract.Assert(System.Boolean)" >
< attribute ctor ="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor" />
< parameter name ="condition" >
< attribute ctor ="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)" >
< argument > 0 </ argument >
</ attribute >
</ parameter >
</ member >
< member name ="M:System.Diagnostics.Contracts.Contract.Assert(System.Boolean,System.String)" >
< attribute ctor ="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor" />
< parameter name ="condition" >
< attribute ctor ="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)" >
< argument > 0 </ argument >
</ attribute >
</ parameter >
</ member >
</ assembly >
* This source code was highlighted with Source Code Highlighter .
And you can also look in the direction of
TerminatesProgramAttribute , if you have methods that always throw an exception.
And lastly
In one article just can not fit. I plan to write a few more articles about the analyzer and its use. Which way my story will go depends on what is interesting to the habrasoobschestvuu: optimistic and pessimistic analyzers, how to programmatically get annotations from sources, or something else.
And yes. Annotate your code with contracts and you will be welcome!