Analyzing whether a method is async void and does not use await in Roslyn

2023年11月23日 58点热度 0人点赞 0条评论
内容目录

Inherit from DiagnosticAnalyzer.

Detecting async void

Register the listener:

        public override void Initialize(AnalysisContext context)
        {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.EnableConcurrentExecution();
            // Register analysis type, only analyze methods
            context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration);
        }

Analyze the code node:

        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var node = context.Node;
            if (node.IsKind(SyntaxKind.MethodDeclaration))
            {
                var syntax = node as MethodDeclarationSyntax;
                if (syntax is null) return;
                AnalyzeMethodDeclarationSyntax(context, syntax);
            }
        }

Determine if the node is an async void method:

        private void AnalyzeMethodDeclarationSyntax(SyntaxNodeAnalysisContext context, MethodDeclarationSyntax syntax)
        {
            var returnType = syntax.ReturnType as PredefinedTypeSyntax;
            if (returnType == null) return;
            if (returnType.Keyword.ValueText != SyntaxFactory.Token(SyntaxKind.VoidKeyword).ValueText) return;

            if (syntax.Modifiers.Any(x => x.ValueText == "async"))
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    descriptor: Rule,
                    location: returnType.GetLocation(),
                    messageArgs: "async void"));
            }
        }

If the developer uses async void code, an error will be prompted.
file

Detecting asynchronous methods not using await

Register the listener:

        public override void Initialize(AnalysisContext context)
        {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.EnableConcurrentExecution();
            context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ExpressionStatement);
        }

Analyze the node:

        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var node = context.Node;
            var ex = node as ExpressionStatementSyntax;
            if (ex is null) return;
            var syntax = ex.Expression as InvocationExpressionSyntax;
            if (syntax is null) return;
            AnalyzeMethodDeclarationSyntax(context, syntax);
        }

Detect and analyze multiple situations:

        private static string[] BlockingCalls = new string[] { "GetAwaiter", "Result", "Wait" };
        private void AnalyzeMethodDeclarationSyntax(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax syntax)
        {
            // Check if the invoked method is an async Task method
            var invokeMethod = context.SemanticModel.GetSymbolInfo(syntax).Symbol as IMethodSymbol;
            if (invokeMethod == null || !ReturnTask(invokeMethod))
            {
                return;
            }

            // Get the name of the invoked method
            var methodSymbol = context
                .SemanticModel
                .GetSymbolInfo(syntax, context.CancellationToken)
                .Symbol as IMethodSymbol;

            // Find this method reference
            var syntaxReference = methodSymbol
                .DeclaringSyntaxReferences
                .FirstOrDefault();

            // Get the definition of this method in the syntax tree
            var methodDeclaration = syntaxReference.GetSyntax(context.CancellationToken) as MethodDeclarationSyntax;
            if (methodDeclaration == null) return;
            // If it's not an asynchronous method
            if (!methodDeclaration.Modifiers.Any(x => x.ValueText == "async")) return;

            // Already awaited
            var isAwaited = syntax.Ancestors().OfType<AwaitExpressionSyntax>().Any();
            if (isAwaited)
            {
                return;
            }

            // Check if await, _ = xxx, or BlockingCalls were used
            bool isInvocationWaited = false;

            foreach (var parent in syntax.Ancestors())
            {
                var assignment = parent as AssignmentExpressionSyntax;
                // Already used discard: _ = xxx
                if (assignment != null)
                {
                    return;
                }

                var parentMemberAccess = parent as MemberAccessExpressionSyntax;
                if (parentMemberAccess?.Name != null)
                {
                    if (BlockingCalls.Any(a => a.Equals(parentMemberAccess.Name.Identifier.ValueText, StringComparison.OrdinalIgnoreCase)))
                    {
                        isInvocationWaited = true;
                        break;
                    }
                }

                if (isInvocationWaited)
                {
                    return;
                }
            }

            context.ReportDiagnostic(Diagnostic.Create(
                descriptor: Rule,
                location: syntax.GetLocation()));
        }

        public static T FirstAncestorOrSelfUnderGivenNode<T>(SyntaxNode node, SyntaxNode parent) where T : SyntaxNode
        {
            var current = node;

            while (current != null && current != parent)
            {
                var temp = current as T;
                if (temp != null)
                {
                    return temp;
                }

                current = current.Parent;
            }

            return null;
        }

        public static bool IsTask(ITypeSymbol type)
        {
            return type.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks" &&
                (type.Name == "Task" || type.Name == "ValueTask");
        }
        public static bool ReturnTask(IMethodSymbol symbol)
        {
            return !symbol.ReturnsVoid && IsTask(symbol.ReturnType);
        }

痴者工良

高级程序员劝退师

文章评论