File: Microsoft.NetCore.Analyzers\Runtime\CSharpDoNotUseStackallocInLoops.cs
Web Access
Project: ..\..\..\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.CSharp.NetAnalyzers\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj (Microsoft.CodeAnalysis.CSharp.NetAnalyzers)
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the MIT license.  See License.txt in the project root for license information.
 
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.NetCore.Analyzers.Runtime;
 
namespace Microsoft.NetCore.CSharp.Analyzers.Runtime
{
    /// <summary>CA2014: Do Not Use Stackalloc In Loops.</summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public sealed class CSharpDoNotUseStackallocInLoopsAnalyzer : DoNotUseStackallocInLoopsAnalyzer
    {
        private static readonly ImmutableArray<SyntaxKind> s_stackallocKinds = ImmutableArray.Create(SyntaxKind.StackAllocArrayCreationExpression);
 
        public sealed override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterSyntaxNodeAction(ctx =>
            {
                // We found a stackalloc.  Walk up from it to see if it's in a loop at any level.
                for (SyntaxNode? node = ctx.Node; node != null; node = node.Parent)
                {
                    switch (node.Kind())
                    {
                        // Don't warn if a local function or lambda containing stackalloc is inside a loop.
                        case SyntaxKind.LocalFunctionStatement:
                        case SyntaxKind.ParenthesizedLambdaExpression:
                        case SyntaxKind.SimpleLambdaExpression:
                        case SyntaxKind.AnonymousMethodExpression:
                            return;
 
                        // foreach loops are special, in that as with other loops we don't want stackallocs
                        // in the body of the loop, but in the expression of a foreach is ok.
                        case SyntaxKind.ForEachStatement:
                        case SyntaxKind.ForEachVariableStatement:
                            var foreachStatement = (CommonForEachStatementSyntax)node;
                            if (foreachStatement.Expression.Contains(ctx.Node))
                            {
                                continue;
                            }
 
                            goto case SyntaxKind.WhileStatement; // fall through
 
                        // Look for loops.  We don't bother with ad-hoc loops via gotos as we're
                        // too likely to incur false positives.
                        // If a stackalloc is in the for loop initializer it is also ok.
                        case SyntaxKind.ForStatement:
                            var forStatement = (ForStatementSyntax)node;
                            if (forStatement.Declaration?.Variables.Any(v => v.Initializer?.Value.Contains(ctx.Node) == true) == true)
                            {
                                continue;
                            }
 
                            goto case SyntaxKind.WhileStatement; // fall through
 
                        case SyntaxKind.WhileStatement:
                        case SyntaxKind.DoStatement:
 
                            // Determine if we should warn for the stackalloc we found in a loop.  In general
                            // if a stackalloc is in a loop, it should be moved out.  However, there's a pattern
                            // where a loop will search for something and then, once it finds it, it stackallocs
                            // as part of processing the thing it found, and exits the loop.  We want to avoid
                            // warning on such cases if possible.  So, as a heuristic, we walk up all the blocks
                            // we find until we reach the first containing loop, and for each block look at all
                            // operations it contains after the target one to see if any is a break or a return.
                            static bool ShouldWarn(IOperation? op, SyntaxNode node)
                            {
                                // Walk up the operation tree, looking at all blocks until we find a loop.
                                for (; op is not null and not ILoopOperation; op = op.Parent)
                                {
                                    // If we hit a block, iterate through all operations in the block
                                    // after the node's position, and see if it's something that will
                                    // execute the loop.
                                    if (op is IBlockOperation block)
                                    {
                                        foreach (IOperation child in block.Operations)
                                        {
                                            if (child.Syntax.SpanStart > node.SpanStart &&
                                                (child is IReturnOperation or IBranchOperation { BranchKind: BranchKind.Break }))
                                            {
                                                // Err on the side of false negatives / caution and say this stackalloc is ok.
                                                // Note, too, it's possible we're breaking out of a nested loop, and the outer loop
                                                // will still cause the stackalloc to be invoked an unbounded number of times,
                                                // but that's difficult to analyze well.
                                                return false;
                                            }
                                        }
                                    }
                                }
 
                                // Warn.
                                return true;
                            }
 
                            // Warn as needed.
                            if (ShouldWarn(ctx.SemanticModel.GetOperationWalkingUpParentChain(ctx.Node, ctx.CancellationToken), ctx.Node))
                            {
                                ctx.ReportDiagnostic(ctx.Node.CreateDiagnostic(Rule));
                            }
 
                            return;
                    }
                }
            }, s_stackallocKinds);
        }
    }
}