﻿# Nullable Reference will not protect you, and here is the proof

Have you ever wanted to get rid of the problem with dereferencing null references? If so, using Nullable Reference types is not your choice\. Do you want to know why? This will be our topic today\.

![0764_Nullable_Reference_And_Roslyn/image1.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image1.png)

We warned you, and it happened\. About a year ago, my colleagues wrote an [article](https://pvs-studio.com/en/blog/posts/csharp/0631/) in which they warned that the introduction of Nullable Reference types will not protect against dereferencing null references\. Now we have an indisputable proof of what we were saying found in the depths of Roslyn\.

## Nullable Reference types

The idea itself of adding_ Nullable Reference_ \(further as NR\) types seems noteworthy to me, since the problem related to dereferencing null references is still relevant to this day\. Nevertheless, the implementation of protection against dereferencing turned out to be extremely unreliable\. According to the idea of creators, only those variables whose type is marked with the "?" symbol can accept the _null_ value\. For example, a variable of the _string?_  type indicates that it might contain _null_, and a variable of the _string_ type might imply the opposite  

However, nobody's stopping us from passing _null_ to _non\-nullable reference_ variables \(further as \- NNR\) of types, because they are not implemented at the IL code level\. The compiler's built\-in static analyzer is responsible for this limitation\. Therefore, this new feature is more of a recommendation\. Here is a simple example showing how it works:

```cpp
#nullable enable
object? nullable = null;
object nonNullable = nullable;
var deref = nonNullable.ToString();
```

As we can see, the _nonNullable_ type is specified as NNR, but we can safely pass _null_ there\. Of course, we will get a warning about converting "Converting null literal or possible null value to non\-nullable type"\. However, we can get round it a bit more aggressively:

```cpp
#nullable enable
object? nullable = null;
object nonNullable = nullable!; // <=
var deref = nonNullable.ToString();
```

One exclamation mark and there are no warnings\. If you're a nitpicker, the following option is also available:

```cpp
#nullable enable
object nonNullable = null!;
var deref = nonNullable.ToString();
```

Here's another example\. Let's create two simple console projects\. In the first we write:

```cpp
namespace NullableTests
{
    public static class Tester
    {
        public static string RetNull() => null;
    }
}
```

In the second one we write:

```cpp
#nullable enable 

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string? nullOrNotNull = NullableTests.Tester.RetNull();
            System.Console.WriteLine(nullOrNotNull.Length);
        }
    }
}
```

Hover the cursor over _nullOrNotNull_ and see this message:

![0764_Nullable_Reference_And_Roslyn/image2.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image2.png)

It's a hint that the string here can't be _null_\. But we already know that it will be _null_ right here\.  Run the project and get the exception:

![0764_Nullable_Reference_And_Roslyn/image3.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image3.png)

Sure, these are just synthetic examples that demonstrate that this feature doesn't guarantee you protection from dereferencing a null reference\. If you consider synthetic examples to be boring and you're wondering where real examples are, don't worry \- they will be further in the article\.

NR types also have another problem \- it's not clear whether they are enabled or not\. For example, the solution has two projects\. One is marked up using this syntax, and the other is not\. When you go to the project with NR types, you can decide that if one is marked up, then all are marked up\. However, this will not be the case\. It turns out that you need to check every time whether nullable context is enabled in a project or file\. Otherwise, you might mistakenly assume that the normal reference type is NNR\.

## How we found proofs 

When developing new diagnostics in the PVS\-Studio analyzer, we always test them on our database of real projects\. This helps for several reasons\. For example, we can: 

* watch "live" the quality of received warnings;
* get rid of some false positives;
* find interesting fragments in the code which you can tell someone about;
* etc\.

One of the new diagnostics \- V3156 found places where exceptions can occur due to potential _null_\. The diagnostic message is as follows: "The argument of the method is not expected to be null"\. Its main point is that a _null_ value can be passed as an argument to a method that does not expect _null_\. This can lead, for example, to an exception or incorrect execution of the called method\. You can read more about this diagnostic rule [here](https://pvs-studio.com/en/docs/warnings/v3156/)\.

## Proofs are here

So here we are in the main part of this article\. Get ready to see real code fragments from the Roslyn project which the diagnostic issued warnings for\. Their underlying idea is that either the NNR type is passed _null_, or there is no checking of the NR type value\. All of this can result in an exception\.

**Example 1**

```cpp
private static Dictionary<object, SourceLabelSymbol>
BuildLabelsByValue(ImmutableArray<LabelSymbol> labels)
{
  ....
  object key;
  var constantValue = label.SwitchCaseLabelConstant;
  if ((object)constantValue != null && !constantValue.IsBad)
  {
    key = KeyForConstant(constantValue);
  }
  else if (labelKind == SyntaxKind.DefaultSwitchLabel)
  {
    key = s_defaultKey;
  }
  else
  {
    key = label.IdentifierNodeOrToken.AsNode();
  }

  if (!map.ContainsKey(key))                // <=
  {
    map.Add(key, label);
  } 
  ....
}
```

V3156 The first argument of the 'ContainsKey' method is not expected to be null\. Potential null value: key\. SwitchBinder\.cs 121

The message states that _key_ is potential _null_\. Let's see where this variable can get this value\. Let's check the _KeyForConstant_ method first:

```cpp
protected static object KeyForConstant(ConstantValue constantValue)
{
  Debug.Assert((object)constantValue != null);
  return constantValue.IsNull ? s_nullKey : constantValue.Value;
}
private static readonly object s_nullKey = new object();
```

Since _s\_nullKey_ is not _null_, see what constantValue\.Value returns:

```cpp
public object? Value
{
  get
  {
    switch (this.Discriminator)
    {
      case ConstantValueTypeDiscriminator.Bad: return null;  // <=
      case ConstantValueTypeDiscriminator.Null: return null; // <=
      case ConstantValueTypeDiscriminator.SByte: return Boxes.Box(SByteValue);
      case ConstantValueTypeDiscriminator.Byte: return Boxes.Box(ByteValue);
      case ConstantValueTypeDiscriminator.Int16: return Boxes.Box(Int16Value);
      ....
      default: throw ExceptionUtilities.UnexpectedValue(this.Discriminator);
    }
  }
}
```

There are two null literals here, but in this case, we won't go into any _case_ with them\. This is due to _IsBad_ and _IsNull_ checks\. However, I would like to draw your attention to the return type of this property\. It is an NR type, but the _KeyForConstant_ method already returns the NNR type\. It turns out that normally the _KeyForConstant_ method can return _null_\.

Another source that can return _null_ is the _AsNode_ method:

```cpp
public SyntaxNode? AsNode()
{
  if (_token != null)
  {
    return null;
  }

  return _nodeOrParent;
}
```

Again, please note the return type of the method — it is NR\. It turns out that when we say that a method can return _null_, it doesn't affect anything\. What's interesting here is the fact that the compiler here does not complain about the conversion from NR to NNR:

![0764_Nullable_Reference_And_Roslyn/image4.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image4.png)

**Example 2**

```cpp
private SyntaxNode CopyAnnotationsTo(SyntaxNode sourceTreeRoot, 
                                     SyntaxNode destTreeRoot)
{  
  var nodeOrTokenMap = new Dictionary<SyntaxNodeOrToken, 
                                      SyntaxNodeOrToken>();
  ....
  if (sourceTreeNodeOrTokenEnumerator.Current.IsNode)
  {
    var oldNode = destTreeNodeOrTokenEnumerator.Current.AsNode();
    var newNode = sourceTreeNodeOrTokenEnumerator.Current.AsNode()
                                       .CopyAnnotationsTo(oldNode);
        
    nodeOrTokenMap.Add(oldNode, newNode); // <=
  }
  ....
}
```

V3156 The first argument of the 'Add' method is not expected to be null\. Potential null value: oldNode\. SyntaxAnnotationTests\.cs 439

Another example with the _AsNode_ function, which was described above\. Only this time _oldNode_ will have the NR type\. While the _key_ described above had the NNR type\.

By the way, I can't help but share an interesting finding with you\. As I described above, when developing diagnostics, we check them on different projects\. When checking the warnings of this rule, I noticed a curious thing\. About 70% of all warnings were issued for methods of the _Dictionary_ class\. In which most of them fell on the _TryGetValue_ method\. This may be because we subconsciously do not expect exceptions from a method that contains the word _try_\. So, check your code for this pattern, you might find something similar\.

**Example 3**

```cpp
private static SymbolTreeInfo TryReadSymbolTreeInfo(
    ObjectReader reader,
    Checksum checksum,
    Func<string, ImmutableArray<Node>, 
    Task<SpellChecker>> createSpellCheckerTask)
{
  ....
  var typeName = reader.ReadString();
  var valueCount = reader.ReadInt32();

  for (var j = 0; j < valueCount; j++)
  {
    var containerName = reader.ReadString();
    var name = reader.ReadString();

    simpleTypeNameToExtensionMethodMap.Add(typeName, // <=
                            new ExtensionMethodInfo(containerName, name)); 
  }
  ....
}
```

V3156 The first argument of the 'Add' method is passed as an argument to the 'TryGetValue' method and is not expected to be null\. Potential null value: typeName\. SymbolTreeInfo\_Serialization\.cs 255

The analyzer says that the problem is in _typeName_\. Let's first make sure that this argument is indeed a potential _null_\. Now look at _ReadString_:

```cpp
public string ReadString() => ReadStringValue();
```

_Ok, check out ReadStringValue_:

```cpp

private string ReadStringValue()
{
  var kind = (EncodingKind)_reader.ReadByte();
  return kind == EncodingKind.Null ? null : ReadStringValue(kind);
}
```

Great, now let's recall where our variable was passed to:

```cpp
simpleTypeNameToExtensionMethodMap.Add(typeName, // <=
                              new ExtensionMethodInfo(containerName,
                                                      name));
```

I think it's high time we took a peek inside the _Add_ method:

```cpp
public bool Add(K k, V v)
{
  ValueSet updated;

  if (_dictionary.TryGetValue(k, out ValueSet set)) // <=
  {
    ....
  }
  ....
}
```

Indeed, if we pass _null_ as the first argument to the _Add_ method, we will get the _ArgumentNullException_\. 

By the way, here's what's interesting \- what if we hover the cursor over _typeName_ in _Visual Studio_, will we see that its type is _string?_:

![0764_Nullable_Reference_And_Roslyn/image5.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image5.png)

The return type of the method is simply _string_:

![0764_Nullable_Reference_And_Roslyn/image6.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image6.png)

In addition, if we create an NNR variable and assign it _typeName_, no error will be output\.

## Let's crash Roslyn

Doing this not out of spite, but for fun, I suggest trying to reproduce one of the examples shown\.

![0764_Nullable_Reference_And_Roslyn/image7.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image7.png)

**Test 1**

Let's take the example described under number 3:

```cpp
private static SymbolTreeInfo TryReadSymbolTreeInfo(
    ObjectReader reader,
    Checksum checksum,
    Func<string, ImmutableArray<Node>, 
    Task<SpellChecker>> createSpellCheckerTask)
{
  ....
  var typeName = reader.ReadString();
  var valueCount = reader.ReadInt32();

  for (var j = 0; j < valueCount; j++)
  {
    var containerName = reader.ReadString();
    var name = reader.ReadString();

    simpleTypeNameToExtensionMethodMap.Add(typeName, // <=
                            new ExtensionMethodInfo(containerName, name)); 
  }
  ....
}
```

To reproduce it, we will need to call the _TryReadSymbolTreeInfo_ method, but it is _private_\. The good thing is that the [class](https://github.com/dotnet/roslyn/blob/18ede13943b0bfae1b44ef078b2f3923159bcd32/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs) with it has the _ReadSymbolTreeInfo\_ForTestingPurposesOnly_ method, which is already _internal_:

```cpp
internal static SymbolTreeInfo ReadSymbolTreeInfo_ForTestingPurposesOnly(
    ObjectReader reader, 
    Checksum checksum)
{
  return TryReadSymbolTreeInfo(reader, checksum,
          (names, nodes) => Task.FromResult(
            new SpellChecker(checksum, 
                             nodes.Select(n => new StringSlice(names, 
                                                               n.NameSpan)))));
}
```

It is very nice that we are simply offered to test the _TryReadSymbolTreeInfo_ method\. So, let's create our own class right here and write the following code:

```cpp
public class CheckNNR
{
  public static void Start()
  {
    using var stream = new MemoryStream();
    using var writer = new BinaryWriter(stream);
    writer.Write((byte)170);
    writer.Write((byte)9);
    writer.Write((byte)0);
    writer.Write(0);
    writer.Write(0);
    writer.Write(1);
    writer.Write((byte)0);
    writer.Write(1);
    writer.Write((byte)0);
    writer.Write((byte)0);
    stream.Position = 0;

    using var reader = ObjectReader.TryGetReader(stream);
    var checksum = Checksum.Create("val");

    SymbolTreeInfo.ReadSymbolTreeInfo_ForTestingPurposesOnly(reader, checksum);
  }
}
```

Now we build _Roslyn_, create a simple console application, include all the necessary dll files, and write this code:

```cpp
static void Main(string[] args)
{
  CheckNNR.Start();
}
```

Run, reach the desired point and see:

![0764_Nullable_Reference_And_Roslyn/image8.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image8.png)

Next, go to the _Add_ method and get the expected exception:

![0764_Nullable_Reference_And_Roslyn/image9.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image9.png)

Let me remind you that the _ReadString_ method returns an NNR type that cannot contain _null_ as intended\. This example once again confirms the relevance of the PVS\-Studio diagnostic rules for searching for dereferencing null links\.

**Test 2**

Well, since we have already started reproducing examples, why not reproduce another one\. This example will not relate to NR types\. However, the same V3156 diagnostic found it, and I wanted to tell you about it\. Here's the code:

```cpp
public SyntaxToken GenerateUniqueName(SemanticModel semanticModel, 
                                      SyntaxNode location, 
                                      SyntaxNode containerOpt, 
                                      string baseName, 
                                      CancellationToken cancellationToken)
{
  return GenerateUniqueName(semanticModel, 
                            location, 
                            containerOpt, 
                            baseName, 
                            filter: null, 
                            usedNames: null,    // <=
                            cancellationToken);
}
```

V3156 The sixth argument of the 'GenerateUniqueName' method is passed as an argument to the 'Concat' method and is not expected to be null\. Potential null value: null\. AbstractSemanticFactsService\.cs 24

I'll be honest: when making this diagnostic, I didn't really expect triggering warnings for simple _null_\. After all, it is quite strange to pass _null_ to a method that throws an exception because of it\. Although, I have seen places where this was justified \(for example, with the _Expression_ class\), but that's not the point now\. 

So, I was very intrigued when I saw this warning\. Let's see what is happening in the _GenerateUniqueName_ method\.

```cpp
public SyntaxToken GenerateUniqueName(SemanticModel semanticModel,
                                      SyntaxNode location, 
                                      SyntaxNode containerOpt,
                                      string baseName, 
                                      Func<ISymbol, bool> filter,
                                      IEnumerable<string> usedNames, 
                                      CancellationToken cancellationToken)
{
  var container = containerOpt ?? location
                       .AncestorsAndSelf()
                       .FirstOrDefault(a => SyntaxFacts.IsExecutableBlock(a) 
                                         || SyntaxFacts.IsMethodBody(a));

  var candidates = GetCollidableSymbols(semanticModel, 
                                        location, 
                                        container, 
                                        cancellationToken);

  var filteredCandidates = filter != null ? candidates.Where(filter) 
                                          : candidates;

  return GenerateUniqueName(baseName, 
                            filteredCandidates.Select(s => s.Name)
                                              .Concat(usedNames));     // <=
}
```

As we can see, there is only one exit point in the method, no exceptions are thrown and there is no _goto_\. In other words, nothing prevents us from passing _usedNames_ to the _Concat_ method and getting the _ArgumentNullException_\. 

But talk is cheap, so let's just do it\. First, we have to find out where we can call this method from\. The method itself is in the [_AbstractSemanticFactsService_](https://github.com/dotnet/roslyn/blob/18ede13943b0bfae1b44ef078b2f3923159bcd32/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs) class\. The class is abstract, so for convenience, let's take the [_CSharpSemanticFactsService_](https://github.com/dotnet/roslyn/blob/18ede13943b0bfae1b44ef078b2f3923159bcd32/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSemanticFactsService.cs) class, which is inherited from it\. In the file of this class, we'll create our own one, which will call the _GenerateUniqueName_ method\. It looks like this:

```cpp
public class DropRoslyn
{
  private const string ProgramText = 
    @"using System;
    using System.Collections.Generic;
    using System.Text
    namespace HelloWorld
    {
      class Program
      {
        static void Main(string[] args)
        {
          Console.WriteLine(""Hello, World!"");
        }
      }
    }";
  
  public void Drop()
  {
    var tree = CSharpSyntaxTree.ParseText(ProgramText);
    var instance = CSharpSemanticFactsService.Instance;
    var compilation = CSharpCompilation
                      .Create("Hello World")
                      .AddReferences(MetadataReference
                                     .CreateFromFile(typeof(string)
                                                     .Assembly
                                                     .Location))
                      .AddSyntaxTrees(tree);
    
    var semanticModel = compilation.GetSemanticModel(tree);
    var syntaxNode1 = tree.GetRoot();
    var syntaxNode2 = tree.GetRoot();
    
    var baseName = "baseName";
    var cancellationToken = new CancellationToken();
    
    instance.GenerateUniqueName(semanticModel, 
                                syntaxNode1, 
                                syntaxNode2, 
                                baseName, 
                                cancellationToken);
  }
}
```

Now we build Roslyn, create a simple console application, include all the necessary dll files, and write this code:

```cpp
class Program
{
  static void Main(string[] args)
  {
    DropRoslyn dropRoslyn = new DropRoslyn();
    dropRoslyn.Drop();
  }
}
```

Run the app and get the following:

![0764_Nullable_Reference_And_Roslyn/image10.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image10.png)

## This is confusing

Let's say we agree with the nullable concept\. It turns out that if we see the NR type, we assume that it may contain a potential _null_\. However, sometimes we can stumble upon cases when the compiler tells us the opposite\. Therefore, we'll walk through several cases where the use of this concept is not intuitive\.

**Case 1**

```cpp
internal override IEnumerable<SyntaxToken>? TryGetActiveTokens(SyntaxNode node)
{
  ....
  var bodyTokens = SyntaxUtilities
                   .TryGetMethodDeclarationBody(node)
                   ?.DescendantTokens();

  if (node.IsKind(SyntaxKind.ConstructorDeclaration, 
                  out ConstructorDeclarationSyntax? ctor))
  {
    if (ctor.Initializer != null)
    {
      bodyTokens = ctor.Initializer
                       .DescendantTokens()
                       .Concat(bodyTokens); // <=
    }
  }
  return bodyTokens;
}
```

V3156 The first argument of the 'Concat' method is not expected to be null\. Potential null value: bodyTokens\. CSharpEditAndContinueAnalyzer\.cs 219

First of all, we check out why _bodyTokens_ is a potential _null_ and notice the _null conditional_ statement:

```cpp
var bodyTokens = SyntaxUtilities
                 .TryGetMethodDeclarationBody(node)
                 ?.DescendantTokens();              // <=
```

If we go inside the _TryGetMethodDeclarationBody_ method, we will see that it can return _null_\. However, it is relatively large, so I'm giving a [link](https://github.com/dotnet/roslyn/blob/18ede13943b0bfae1b44ef078b2f3923159bcd32/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs) for you to see it for yourself\. So, it's all clear with _bodyTokens_, but I'd like to point out the _ctor_ argument:

```cpp
if (node.IsKind(SyntaxKind.ConstructorDeclaration, 
                out ConstructorDeclarationSyntax? ctor))
```

As we can see, its type is set as NR\. At the same time, here's dereference in the line below:

```cpp
if (ctor.Initializer != null)
```

This combination is a bit ominous\. Nonetheless, you will say that, most likely, if _IsKind_ returns _true_, then _ctor_ is definitely not _null_\. So it is:

```cpp
public static bool IsKind<TNode>(
    [NotNullWhen(returnValue: true)] this SyntaxNode? node, // <=
    SyntaxKind kind,
    [NotNullWhen(returnValue: true)] out TNode? result)     // <=
    where TNode : SyntaxNode 
{
  if (node.IsKind(kind))
  {
    result = (TNode)node;
    return true;
  }

  result = null;
  return false;
}
```

Special attributes used here indicate at which output value the parameters will not be _null_\. We can make sure of it by looking at the logic of the _IsKind_ method\. It turns out that the _ctor_ type must be NNR inside the condition\. The compiler is aware of it and says that _ctor_ inside the condition will not be _null_\. But if we want to get it ourselves, we have to go inside the _IsKind_ method and notice the attribute there\. Otherwise, it looks like dereferencing the NR variable without checking for _null_\. We can try making this a bit more visible as follows:

```cpp
if (node.IsKind(SyntaxKind.ConstructorDeclaration, 
                out ConstructorDeclarationSyntax? ctor))
{
    if (ctor!.Initializer != null) // <=
    {
      ....
    }
}
```

**Case 2**

```cpp
public TextSpan GetReferenceEditSpan(InlineRenameLocation location, 
                                     string triggerText, 
                                     CancellationToken cancellationToken)
{
  var searchName = this.RenameSymbol.Name;
  if (_isRenamingAttributePrefix)
  {
    searchName = GetWithoutAttributeSuffix(this.RenameSymbol.Name);
  }

  var index = triggerText.LastIndexOf(searchName,            // <=
                                      StringComparison.Ordinal);
  ....
}
```

V3156 The first argument of the 'LastIndexOf' method is not expected to be null\. Potential null value: searchName\. AbstractEditorInlineRenameService\.SymbolRenameInfo\.cs 126

We are interested in the _searchName_ variable\. _null_ can be written into it after calling the _GetWithoutAttributeSuffix_ method, but it's not that simple\. Let's see what happens in it:

```cpp
private string GetWithoutAttributeSuffix(string value)
    => value.GetWithoutAttributeSuffix(isCaseSensitive:
                _document.GetRequiredLanguageService<ISyntaxFactsService>()
                         .IsCaseSensitive)!;
```

Let's dig a bit deeper:

```cpp
internal static string? GetWithoutAttributeSuffix(
            this string name,
            bool isCaseSensitive)
{
  return TryGetWithoutAttributeSuffix(name, isCaseSensitive, out var result) 
         ? result : null;
}
```

It turns out that the _TryGetWithoutAttributeSuffix_ method will return either _result_ or _null_\. And the method returns the NR type\. However, when we go back a step, we notice that the method type has suddenly changed to NNR\. This is due to the hidden sign "\!":

```cpp
_document.GetRequiredLanguageService<ISyntaxFactsService>()
         .IsCaseSensitive)!; // <=
```

By the way, it is quite tricky to notice it in Visual Studio:

![0764_Nullable_Reference_And_Roslyn/image11.png](https://import.viva64.com/docx/blog/0764_Nullable_Reference_And_Roslyn/image11.png)

By setting it, the developer tells us that the method will never return _null_\. Although, looking at the previous examples and going into the _TryGetWithoutAttributeSuffix_ method, I personally can't be sure:

```cpp
internal static bool TryGetWithoutAttributeSuffix(
            this string name,
            bool isCaseSensitive,
            [NotNullWhen(returnValue: true)] out string? result)
{
  if (name.HasAttributeSuffix(isCaseSensitive))
  {
    result = name.Substring(0, name.Length - AttributeSuffix.Length);
    return true;
  }

  result = null;
  return false;
}
```

## Conclusion

In conclusion, I would like to note that the attempt to save us from unnecessary _null_ checks is a great idea\. However, NR types are rather advisory in nature, because no one strictly forbids us to pass _null_ to the NNR type\. Therefore, the corresponding PVS\-Studio rules remain relevant\. For example, such as [V3080](https://pvs-studio.com/en/docs/warnings/v3080/) or [V3156](https://pvs-studio.com/en/docs/warnings/v3156/)\.

All the best to you and thank you for your attention\.