Combining a null-coalescing operator with the "continue" keyword reports S2259

  • versions used: SonarCloud & SonarLint, C#, Visual Studio 2019
  • Rule affected: csharpsquid:S2259 “’‘providerCourses’ is null on at least one execution path.”
  • minimal code sample to reproduce (with analysis parameter, and potential instructions to compile).

Combining a null-coalescing operator with the “continue” keyword in a for loop still reports the variable usage with is null on at least one execution path

Sample 1, no loop + null coalescing operator:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sample1
{
    class Sample1
    {
        static void Main(string[] args)
        {
            var providerCourse = new ProviderCourse
            {
                Items = new List<string>
                    {
                        "item1",
                        "item2"
                    }
            };

            if (!providerCourse?.Items?.Any() ?? true)
            {
                Console.WriteLine("FAIL");
                throw new InvalidOperationException();
            }

            // Here providerCourse is not reported by SonarCloud/SonarLint
            var _ = providerCourse.Items.Where(items => items == "item1"); 
        }
    }

    public class ProviderCourse
    {
        public IEnumerable<string> Items { get; set; }
    }
}

Sample 2, loop with null || !.any():

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sample2
{
    class Sample2
    {
        static void Main(string[] args)
        {
            var providerCourses = new List<ProviderCourse>
            {
                new ProviderCourse
                {
                    Items = new List<string>
                    {
                        "item1",
                        "item2"
                    }
                },
                new ProviderCourse
                {
                    Items = new List<string>
                    {
                        "item1",
                        "item2"
                    }
                }
            };

            foreach (var providerCourse in providerCourses)
            {
                if (providerCourse?.Items == null || !providerCourse.Items.Any())
                {
                    Console.WriteLine("FAIL");
                    continue;
                }

                // Here providerCourse is not reported by SonarCloud/SonarLint
                var _ = providerCourse.Items.Where(items => items == "item1");
            }
        }
    }

    public class ProviderCourse
    {
        public IEnumerable<string> Items { get; set; }
    }
}

Sample 3, loop + combine null and !.any() check:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sample3
{
    class Sample3
    {
        static void Main(string[] args)
        {
            var providerCourses = new List<ProviderCourse>
            {
                new ProviderCourse
                {
                    Items = new List<string>
                    {
                        "item1",
                        "item2"
                    }
                },
                new ProviderCourse
                {
                    Items = new List<string>
                    {
                        "item1",
                        "item2"
                    }
                }
            };

            foreach (var providerCourse in providerCourses)
            {
                if (!providerCourse?.Items?.Any() ?? true)
                {
                    Console.WriteLine("FAIL");
                    continue;
                }

                // Here providerCourse is reported with "‘providerCourses’ is null on at least one execution path."
                // While behaving exactly like Sample 2.
                var _ = providerCourse.Items.Where(items => items == "item1");
            }
        }
    }

    public class ProviderCourse
    {
        public IEnumerable<string> Items { get; set; }
    }
}

In case of Sample3 “providerCourse” can’t ever be null, and should not be reported as such.

It might actually be related to S2259 FP: Null conditional combined with null coalescing · Issue #4537 · SonarSource/sonar-dotnet · GitHub. Although we don’t use an explicit else clause and in that case it’s not reported (See sample1).

Hello @AdeZwart ! Thank you for your feedback!

Indeed, I was able to reproduce that issue locally.

Currently we are not planning to improve Symbolic Execution based rules (such as S2259) that rely on non-Roslyn CFGs. We’ve already planned moving all of our CFG-based rules to Roslyn CFG as well as creating a new SE engine that supports those kind of CFGs. As a part of this effort, we will try to tackle all the FPs those rules have.

Feel free to track the progress of your issue here.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.