Explorar o código

更新ETTask CancelToken分析器 (#440)

* 注释cancelToken分析器
susices %!s(int64=3) %!d(string=hai) anos
pai
achega
77bc3137b6

+ 99 - 18
Share/Analyzer/Analyzer/ETCancellationTokenAnalyzer.cs

@@ -1,9 +1,11 @@
-using System.Collections.Immutable;
+using System;
+using System.Collections.Immutable;
 using System.Linq;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
 using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.FlowAnalysis;
 
 namespace ET.Analyzer
 {
@@ -13,7 +15,7 @@ namespace ET.Analyzer
         public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
             CheckETCancellTokenAfterAwaitAnalyzerRule.Rule,
             AwaitExpressionCancelTokenParamAnalyzerRule.Rule, AsyncMethodWithCancelTokenParamAnalyzerRule.Rule,
-            ExpressionWithCancelTokenParamAnalyzerRule.Rule, ETTaslAsyncMethodHasCancelTokenAnalyzerRule.Rule);
+            ExpressionWithCancelTokenParamAnalyzerRule.Rule);
 
         public override void Initialize(AnalysisContext context)
         {
@@ -63,12 +65,11 @@ namespace ET.Analyzer
                 return;
             }
 
+            bool isGenericReturnTYpe = methodDeclarationSyntax.ReturnType.IsKind(SyntaxKind.GenericName);
+
             // 检测是否含有cancelToken参数
             if (!methodSymbol.HasParameterType(Definition.ETCancellationToken, out IParameterSymbol? cancelTokenSymbol) || cancelTokenSymbol == null)
             {
-                Diagnostic diagnostic = Diagnostic.Create(ETTaslAsyncMethodHasCancelTokenAnalyzerRule.Rule,
-                    methodDeclarationSyntax.ParameterList.GetLocation());
-                context.ReportDiagnostic(diagnostic);
                 return;
             }
 
@@ -100,13 +101,6 @@ namespace ET.Analyzer
                     continue;
                 }
 
-                // await函数调用后 是否判断了canceltoken
-                if (!this.HasCheckCancelTokenAfterAwait(awaitExpressionSyntax, cancelTokenSymbol, context))
-                {
-                    Diagnostic diagnostic = Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
-                    context.ReportDiagnostic(diagnostic);
-                }
-
                 // 跳过await字段的表达式
                 InvocationExpressionSyntax? awaitInvocationSyntax = awaitExpressionSyntax.Expression as InvocationExpressionSyntax;
                 if (awaitInvocationSyntax == null)
@@ -120,25 +114,112 @@ namespace ET.Analyzer
                     Diagnostic diagnostic = Diagnostic.Create(AwaitExpressionCancelTokenParamAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
                     context.ReportDiagnostic(diagnostic);
                 }
+
+                StatementSyntax? statementSyntax = awaitExpressionSyntax.GetNeareastAncestor<StatementSyntax>();
+                if (statementSyntax == null)
+                {
+                    continue;
+                }
+
+                BasicBlock? block = AnalyzerHelper.GetAwaitStatementControlFlowBlock(statementSyntax, awaitExpressionSyntax, context.SemanticModel);
+
+                if (block == null)
+                {
+                    if (statementSyntax.IsKind(SyntaxKind.LocalDeclarationStatement) &&
+                        !HasCheckCancelTokenAfterAwaitForLocalDeclaration(statementSyntax, cancelTokenSymbol, context))
+                    {
+                        Diagnostic diagnostic =
+                                Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
+                        context.ReportDiagnostic(diagnostic);
+                        //throw new Exception($"block == null {statementSyntax.IsKind(SyntaxKind.LocalDeclarationStatement)} file {awaitExpressionSyntax.SyntaxTree.FilePath}");
+                    }
+
+                    continue;
+                }
+
+                bool isMethodExitPoint = IsMethodExitPoint(block, statementSyntax, out int statementIndex);
+
+                if (isMethodExitPoint)
+                {
+                    if (!isGenericReturnTYpe)
+                    {
+                        continue;
+                    }
+
+                    Diagnostic diagnostic = Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
+                    context.ReportDiagnostic(diagnostic);
+                    continue;
+                }
+
+                // await函数调用后 是否判断了canceltoken
+                if (!this.HasCheckCancelTokenAfterAwaitForExpression(statementSyntax, cancelTokenSymbol, block, statementIndex, context))
+                {
+                    Diagnostic diagnostic = Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
+                    context.ReportDiagnostic(diagnostic);
+                }
             }
         }
 
+        private bool IsMethodExitPoint(BasicBlock block, StatementSyntax statementSyntax, out int statementIndex)
+        {
+            IOperation statementOperation = block.Operations.First(x => x.Syntax.Contains(statementSyntax));
+            // 如果表达式在block最后一个,是出口函数则跳过 否则报错
+            statementIndex = block.Operations.IndexOf(statementOperation);
+            if (statementIndex == block.Operations.Length - 1)
+            {
+                return block.FallThroughSuccessor?.Destination?.Kind == BasicBlockKind.Exit;
+            }
+
+            return false;
+        }
+
         /// <summary>
         /// 调用await函数后,是否判断了所在函数传入的canceltoken参数是否取消 
         /// </summary>
-        private bool HasCheckCancelTokenAfterAwait(AwaitExpressionSyntax awaitExpression, IParameterSymbol cancelTokenSymbol,
+        private bool HasCheckCancelTokenAfterAwaitForExpression(StatementSyntax statementSyntax, IParameterSymbol cancelTokenSymbol, BasicBlock block,
+        int statementIndex,
         SyntaxNodeAnalysisContext context)
         {
+            // 判断表达式是否为block最后一个
+            if (block.Operations.Length - 1 == statementIndex)
+            {
+                return false;
+            }
+
             // 检查await表达式的下一个表达式是否为判断 cancelToken.IsCancel()
-            StatementSyntax? statementSyntax = awaitExpression.GetNeareastAncestor<StatementSyntax>();
+            StatementSyntax? nextStatement =
+                    block.Operations[statementIndex + 1].Syntax.DescendantNodesAndSelf().OfType<StatementSyntax>().FirstOrDefault();
+            if (nextStatement == null)
+            {
+                return false;
+            }
+
+            return IsCheckCancelTokenStatement(nextStatement, cancelTokenSymbol);
+        }
 
-            if (statementSyntax == null)
+        /// <summary>
+        /// 调用await函数后,是否判断了所在函数传入的canceltoken参数是否取消
+        /// LocalDeclaration 表达式 无法使用控制流图
+        /// </summary>
+        private bool HasCheckCancelTokenAfterAwaitForLocalDeclaration(StatementSyntax statementSyntax, IParameterSymbol cancelTokenSymbol,
+        SyntaxNodeAnalysisContext context)
+        {
+            // 检查await表达式的下一个表达式是否为判断 cancelToken.IsCancel()
+            StatementSyntax? nextStatement = statementSyntax.NextNode() as StatementSyntax;
+            if (nextStatement == null)
             {
-                return true;
+                return false;
             }
 
-            SyntaxNode? nextNode = statementSyntax.NextNode();
-            if (nextNode is not IfStatementSyntax ifStatementSyntax)
+            return IsCheckCancelTokenStatement(nextStatement, cancelTokenSymbol);
+        }
+
+        /// <summary>
+        /// 判断下个表达式是否为检查canceltoken
+        /// </summary>
+        private bool IsCheckCancelTokenStatement(StatementSyntax nextStatement, IParameterSymbol cancelTokenSymbol)
+        {
+            if (nextStatement is not IfStatementSyntax ifStatementSyntax)
             {
                 return false;
             }

+ 0 - 2
Share/Analyzer/Config/DiagnosticIds.cs

@@ -40,7 +40,5 @@
 
         public const string ExpressionWithCancelTokenParamAnalyzerRuleId = "ET0019";
 
-        public const string ETTaslAsyncMethodHasCancelTokenAnalyzerRuleId = "ET0020";
-
     }
 }

+ 0 - 19
Share/Analyzer/Config/DiagnosticRules.cs

@@ -218,23 +218,4 @@ namespace ET.Analyzer
                     true,
                     Description);
     }
-
-    public static class ETTaslAsyncMethodHasCancelTokenAnalyzerRule
-    {
-        private const string Title = "返回ETTask的异步函数必须含有ETCancelToken参数";
-    
-        private const string MessageFormat = "返回ETTask的异步函数必须含有ETCancelToken参数";
-    
-        private const string Description = "返回ETTask的异步函数必须含有ETCancelToken参数.";
-    
-        public static readonly DiagnosticDescriptor Rule =
-                new DiagnosticDescriptor(DiagnosticIds.ETTaslAsyncMethodHasCancelTokenAnalyzerRuleId,
-                    Title,
-                    MessageFormat,
-                    DiagnosticCategories.All,
-                    DiagnosticSeverity.Error,
-                    true,
-                    Description);
-    }
-
 }

+ 40 - 1
Share/Analyzer/Extension/AnalyzerHelper.cs

@@ -1,7 +1,12 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Linq;
 using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.FlowAnalysis;
+using Exception = System.Exception;
 
 namespace ET.Analyzer
 {
@@ -320,5 +325,39 @@ namespace ET.Analyzer
             
             return syntaxNode.Parent.ChildNodes().ElementAt(index+1);
         }
+        
+        /// <summary>
+        /// 获取await表达式所在的控制流block
+        /// </summary>
+        public static BasicBlock? GetAwaitStatementControlFlowBlock(StatementSyntax statementSyntax,AwaitExpressionSyntax awaitExpressionSyntax ,SemanticModel semanticModel)
+        {
+            // 跳过 return 表达式
+            if (statementSyntax.IsKind(SyntaxKind.ReturnStatement))
+            {
+                return null;
+            }
+            
+            var methodSyntax = statementSyntax.GetNeareastAncestor<MethodDeclarationSyntax>();
+            if (methodSyntax==null)
+            {
+                return null;
+            }
+
+            // 构建表达式所在函数的控制流图
+            var controlFlowGraph = ControlFlowGraph.Create(methodSyntax, semanticModel);
+
+            if (controlFlowGraph==null)
+            {
+                return null;
+            }
+
+            if (statementSyntax is LocalDeclarationStatementSyntax)
+            {
+                return null;
+            }
+            
+            BasicBlock? block = controlFlowGraph.Blocks.FirstOrDefault(x => x.Operations.Any(y => y.Syntax.Contains(statementSyntax)));
+            return block;
+        }
     }
 }