S2583 false positive with a function to check if values in an array align

  • What language is this for?
    C#
  • Which rule?
    S2583 C# static code analysis
  • Why do you believe it’s a false-positive/false-negative?
    The code is reachable and works fine. I appended a program that proves this
  • Are you using
    • SonarLint
      Visual studio 2022, SonarLint version 7.3.0.77872
  • How can we reproduce the problem? Give us a self-contained snippet of code (formatted text, no screenshots)

This is a connect 4 code that I was working with. The IsWinningMove() Method gives me the false positive in the if statement checking the amount of tokens when checking if there are 4 tokens in a row.
I understand the code is quite tricky to follow so I thought the linter probably had issues with it, I made an alternative version with cleaner syntax to see if it would help but it doesn’t.

The main function creates a board and then executes the test so you can see that it indeed passes and it can evaluate to true unlike what the linter suggest.

If you need more information I would be happy to provide it

public class Program
{
    public static void Main(string[] args)
    {
        Board board = new Board();
        board.CreatePosition();
        bool result = board.IsWinningMove(0);
        Console.WriteLine(result); // prints true, so the check succeeds even though sonarlint says it won't

        bool resultAlt = board.IsWinningMoveAlternative(0);
        Console.WriteLine(resultAlt);
    }

    class Board
    {
        public Board()
        {
            _currentPlayerIndex = 0;
            _board = new int[10, 10];
            _highestRowIndex = new int[10];
            for (int i = 0; i < BoardWidth; i++)
                _highestRowIndex[i] = 9;
        }
        public void CreatePosition()
        {
            _board[1, 9] = 1;
            _board[2, 9] = 1;
            _board[3, 9] = 1;
            _highestRowIndex[1] = 8;
            _highestRowIndex[2] = 8;
            _highestRowIndex[3] = 8;
        }

        private readonly int[,] _board;
        private int BoardWidth => _board.GetLength(0);
        private int BoardHeight => _board.GetLength(1);
        private int CurrentTurnToken => _currentPlayerIndex == 0 ? 1 : 2;
        private int _currentPlayerIndex;
        private readonly int[] _highestRowIndex;
        public bool IsWinningMove(int col)
        {
            // vertical check goes here but omitted for brevity

            for (int dy = -1; dy <= 1; dy++)
            {                                       // Iterate on horizontal (dy = 0) or two diagonal directions (dy = -1 or dy = 1).
                int numberOfTokens = 0;             // counter of the number of tokens of current player surrounding the played token in tested direction.
                for (int dx = -1; dx <= 1; dx += 2) // count continuous tokens of current player on the left, then right of the played column.
                    for (int x = col + dx, y = _highestRowIndex[col] + dx * dy;
                        x >= 0 && x < BoardWidth && y >= 0 && y < BoardHeight && _board[x, y] == CurrentTurnToken;
                        numberOfTokens++)
                    {
                        x += dx;
                        y += dx * dy;
                    }
                // false positive in this if statement
                if (numberOfTokens >= 3)            // there is an alignment if at least 3 other tokens of the current user 
                    return true;                    // are surrounding the played token in the tested direction.
            }                                       // honestly I don't really know how this works but it does lmao
            return false;
        }
        public bool IsWinningMoveAlternative(int col)
        {
            // vertical check goes here but omitted for brevity

            for (int dy = -1; dy <= 1; dy++)
            {                                       // Iterate on horizontal (dy = 0) or two diagonal directions (dy = -1 or dy = 1).
                int numberOfTokens = 0;             // counter of the number of tokens of current player surrounding the played token in tested direction.
                for (int dx = -1; dx <= 1; dx += 2) // count continuous tokens of current player on the left, then right of the played column.
                {
                    int x = col + dx;
                    int y = _highestRowIndex[col] + dx * dy;
                    while (x >= 0 && x < BoardWidth && y >= 0 && y < BoardHeight && _board[x, y] == CurrentTurnToken)
                    {
                        numberOfTokens++;
                        x += dx;
                        y += dx * dy;
                    }
                }
                // changing the stuff above to something more readable still results in the error
                if (numberOfTokens >= 3)            // there is an alignment if at least 3 other tokens of the current user 
                    return true;                    // are surrounding the played token in the tested direction.
            }                                       // honestly I don't really know how this works but it does lmao
            return false;
        }
    }
}

funnily enough adding this line makes it go away

for (int dy = -1; dy <= 1; dy++)
{                                       
    int numberOfTokens = 0;             
    for (int dx = -1; dx <= 1; dx += 2) 
        for (int x = col + dx, y = _highestRowIndex[col] + dx * dy;
            x >= 0 && x < BoardWidth && y >= 0 && y < BoardHeight && _board[x, y] == CurrentTurnToken; numberOfTokens++)
        {
            x += dx;
            y += dx * dy;
            numberOfTokens += 0; // added this line that does nothing
        }
    if (numberOfTokens >= 3)            
        return true;                    
}                                       
return false;

I’m not sure why

Hello @traso56,

Welcome to the community!

Thank you for reporting this false positive.
It is a known issue within our analyzer, see one of the open issues.

To give an overview, this rule is complicated and performance-heavy, to reduce the impact it could have on the build, we consciously simulate the loop execution only 2 times. Thus, your condition never resolves to true.

You can find more details in the linked issue.

Unfortunately, this FP might not be fixed anytime soon as it would significantly increase the build time to simulate more loop iterations.

Regarding the issue going away in this example, SonarLint 7.3.0 embeds the C# analyzer 9.8.0, in which the rule does not raise. But it raises in the latest version of the C# analyzer (9.12.0).

Have a nice day.

Ah I see, well thanks for the answer. With this information this would be the simplest code that triggers it

int n = 0;
for (int i = 0; i < 3; i++)
{
    n++;
}
if (n > 2)
{
    Console.WriteLine("test");
}

I hope a solution appears soon. Also thanks for this linter, it has caught many bugs and issues in my code which is really cool

2 Likes

Hello @traso56,

We plan on tackling as many issues related to this rule as possible, hopefully by the end of the year.
This scenario will be examined to see if we can improve its behavior and reduce the noise.

Unfortunately, as I mentioned in my previous message, we cannot guarantee we will find a suitable solution that gives high accuracy while keeping the analysis under an acceptable duration.
We have to find the right balance.

Thank you for the good words and I appreciate your understanding :pray:

1 Like