📜 ⬆️ ⬇️

ReSharper: Analysis on NullReferenceException and Contracts for It

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!

Source: https://habr.com/ru/post/103494/


All Articles