ETCancellationTokenAnalyzer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. using System;
  2. using System.Collections.Immutable;
  3. using System.Linq;
  4. using Microsoft.CodeAnalysis;
  5. using Microsoft.CodeAnalysis.CSharp;
  6. using Microsoft.CodeAnalysis.CSharp.Syntax;
  7. using Microsoft.CodeAnalysis.Diagnostics;
  8. using Microsoft.CodeAnalysis.FlowAnalysis;
  9. namespace ET.Analyzer
  10. {
  11. [DiagnosticAnalyzer(LanguageNames.CSharp)]
  12. public class ETCancellationTokenAnalyzer: DiagnosticAnalyzer
  13. {
  14. public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
  15. CheckETCancellTokenAfterAwaitAnalyzerRule.Rule,
  16. AwaitExpressionCancelTokenParamAnalyzerRule.Rule, AsyncMethodWithCancelTokenParamAnalyzerRule.Rule,
  17. ExpressionWithCancelTokenParamAnalyzerRule.Rule);
  18. public override void Initialize(AnalysisContext context)
  19. {
  20. if (!AnalyzerGlobalSetting.EnableAnalyzer)
  21. {
  22. return;
  23. }
  24. context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
  25. context.EnableConcurrentExecution();
  26. //context.RegisterSyntaxNodeAction(this.AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
  27. }
  28. private void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
  29. {
  30. if (!AnalyzerHelper.IsAssemblyNeedAnalyze(context.Compilation.AssemblyName, AnalyzeAssembly.AllHotfix))
  31. {
  32. return;
  33. }
  34. if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax)
  35. {
  36. return;
  37. }
  38. // 检查含有ETCancelToken参数的函数调用 是否传入null 或未赋值
  39. foreach (InvocationExpressionSyntax? invocationExpressionSyntax in methodDeclarationSyntax.DescendantNodes<InvocationExpressionSyntax>())
  40. {
  41. if (this.CancelTokenArguIsNullOrNotSet(invocationExpressionSyntax, context))
  42. {
  43. Diagnostic diagnostic = Diagnostic.Create(ExpressionWithCancelTokenParamAnalyzerRule.Rule,
  44. invocationExpressionSyntax.GetLocation());
  45. context.ReportDiagnostic(diagnostic);
  46. }
  47. }
  48. // 忽略非异步函数
  49. IMethodSymbol? methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclarationSyntax);
  50. if (methodSymbol is not { IsAsync: true })
  51. {
  52. return;
  53. }
  54. string methodReturnType = $"{methodSymbol.ReturnType.ContainingNamespace}.{methodSymbol.ReturnType.Name}";
  55. if (methodReturnType != Definition.ETTaskFullName)
  56. {
  57. return;
  58. }
  59. bool isGenericReturnTYpe = methodDeclarationSyntax.ReturnType.IsKind(SyntaxKind.GenericName);
  60. // 检测是否含有cancelToken参数
  61. if (!methodSymbol.HasParameterType(Definition.ETCancellationToken, out IParameterSymbol? cancelTokenSymbol) || cancelTokenSymbol == null)
  62. {
  63. return;
  64. }
  65. // 函数定义处 ETcanceltoken参数 是否有默认值
  66. if (cancelTokenSymbol.HasExplicitDefaultValue)
  67. {
  68. SyntaxNode? cancelTokenParamSyntax = cancelTokenSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax();
  69. if (cancelTokenParamSyntax == null)
  70. {
  71. cancelTokenParamSyntax = methodDeclarationSyntax;
  72. }
  73. Diagnostic diagnostic = Diagnostic.Create(AsyncMethodWithCancelTokenParamAnalyzerRule.Rule, cancelTokenParamSyntax.GetLocation());
  74. context.ReportDiagnostic(diagnostic);
  75. }
  76. foreach (AwaitExpressionSyntax? awaitExpressionSyntax in methodDeclarationSyntax.DescendantNodes<AwaitExpressionSyntax>())
  77. {
  78. // 跳过await 返回值不是ETTask的
  79. TypeInfo awaitExpressionTypeSymbol = context.SemanticModel.GetTypeInfo(awaitExpressionSyntax.Expression);
  80. if (awaitExpressionTypeSymbol.Type == null)
  81. {
  82. continue;
  83. }
  84. string awaitExpressionType = $"{awaitExpressionTypeSymbol.Type.ContainingNamespace}.{awaitExpressionTypeSymbol.Type.Name}";
  85. if (awaitExpressionType != Definition.ETTaskFullName)
  86. {
  87. continue;
  88. }
  89. // 跳过await字段的表达式
  90. InvocationExpressionSyntax? awaitInvocationSyntax = awaitExpressionSyntax.Expression as InvocationExpressionSyntax;
  91. if (awaitInvocationSyntax == null)
  92. {
  93. continue;
  94. }
  95. // await 函数是否带canceltoken参数
  96. if (!HasCancelTokenInAwait(awaitInvocationSyntax, cancelTokenSymbol, context))
  97. {
  98. Diagnostic diagnostic = Diagnostic.Create(AwaitExpressionCancelTokenParamAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
  99. context.ReportDiagnostic(diagnostic);
  100. }
  101. StatementSyntax? statementSyntax = awaitExpressionSyntax.GetNeareastAncestor<StatementSyntax>();
  102. if (statementSyntax == null)
  103. {
  104. continue;
  105. }
  106. BasicBlock? block = AnalyzerHelper.GetAwaitStatementControlFlowBlock(statementSyntax, awaitExpressionSyntax, context.SemanticModel);
  107. if (block == null)
  108. {
  109. if (statementSyntax.IsKind(SyntaxKind.LocalDeclarationStatement) &&
  110. !HasCheckCancelTokenAfterAwaitForLocalDeclaration(statementSyntax, cancelTokenSymbol, context))
  111. {
  112. Diagnostic diagnostic =
  113. Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
  114. context.ReportDiagnostic(diagnostic);
  115. //throw new Exception($"block == null {statementSyntax.IsKind(SyntaxKind.LocalDeclarationStatement)} file {awaitExpressionSyntax.SyntaxTree.FilePath}");
  116. }
  117. continue;
  118. }
  119. bool isMethodExitPoint = IsMethodExitPoint(block, statementSyntax, out int statementIndex);
  120. if (isMethodExitPoint)
  121. {
  122. if (!isGenericReturnTYpe)
  123. {
  124. continue;
  125. }
  126. Diagnostic diagnostic = Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
  127. context.ReportDiagnostic(diagnostic);
  128. continue;
  129. }
  130. // await函数调用后 是否判断了canceltoken
  131. if (!this.HasCheckCancelTokenAfterAwaitForExpression(statementSyntax, cancelTokenSymbol, block, statementIndex, context))
  132. {
  133. Diagnostic diagnostic = Diagnostic.Create(CheckETCancellTokenAfterAwaitAnalyzerRule.Rule, awaitExpressionSyntax.GetLocation());
  134. context.ReportDiagnostic(diagnostic);
  135. }
  136. }
  137. }
  138. private bool IsMethodExitPoint(BasicBlock block, StatementSyntax statementSyntax, out int statementIndex)
  139. {
  140. IOperation statementOperation = block.Operations.First(x => x.Syntax.Contains(statementSyntax));
  141. // 如果表达式在block最后一个,是出口函数则跳过 否则报错
  142. statementIndex = block.Operations.IndexOf(statementOperation);
  143. if (statementIndex == block.Operations.Length - 1)
  144. {
  145. return block.FallThroughSuccessor?.Destination?.Kind == BasicBlockKind.Exit;
  146. }
  147. return false;
  148. }
  149. /// <summary>
  150. /// 调用await函数后,是否判断了所在函数传入的canceltoken参数是否取消
  151. /// </summary>
  152. private bool HasCheckCancelTokenAfterAwaitForExpression(StatementSyntax statementSyntax, IParameterSymbol cancelTokenSymbol, BasicBlock block,
  153. int statementIndex,
  154. SyntaxNodeAnalysisContext context)
  155. {
  156. // 判断表达式是否为block最后一个
  157. if (block.Operations.Length - 1 == statementIndex)
  158. {
  159. return false;
  160. }
  161. // 检查await表达式的下一个表达式是否为判断 cancelToken.IsCancel()
  162. StatementSyntax? nextStatement =
  163. block.Operations[statementIndex + 1].Syntax.DescendantNodesAndSelf().OfType<StatementSyntax>().FirstOrDefault();
  164. if (nextStatement == null)
  165. {
  166. return false;
  167. }
  168. return IsCheckCancelTokenStatement(nextStatement, cancelTokenSymbol);
  169. }
  170. /// <summary>
  171. /// 调用await函数后,是否判断了所在函数传入的canceltoken参数是否取消
  172. /// LocalDeclaration 表达式 无法使用控制流图
  173. /// </summary>
  174. private bool HasCheckCancelTokenAfterAwaitForLocalDeclaration(StatementSyntax statementSyntax, IParameterSymbol cancelTokenSymbol,
  175. SyntaxNodeAnalysisContext context)
  176. {
  177. // 检查await表达式的下一个表达式是否为判断 cancelToken.IsCancel()
  178. StatementSyntax? nextStatement = statementSyntax.NextNode() as StatementSyntax;
  179. if (nextStatement == null)
  180. {
  181. return false;
  182. }
  183. return IsCheckCancelTokenStatement(nextStatement, cancelTokenSymbol);
  184. }
  185. /// <summary>
  186. /// 判断下个表达式是否为检查canceltoken
  187. /// </summary>
  188. private bool IsCheckCancelTokenStatement(StatementSyntax nextStatement, IParameterSymbol cancelTokenSymbol)
  189. {
  190. if (nextStatement is not IfStatementSyntax ifStatementSyntax)
  191. {
  192. return false;
  193. }
  194. string conditionStr = ifStatementSyntax.Condition.ToString().Replace(" ", string.Empty);
  195. if (conditionStr != $"{cancelTokenSymbol.Name}.IsCancel()")
  196. {
  197. return false;
  198. }
  199. // 检查判断表达式内是否直接return
  200. if (ifStatementSyntax.Statement.GetFirstChild<ReturnStatementSyntax>() == null)
  201. {
  202. return false;
  203. }
  204. return true;
  205. }
  206. /// <summary>
  207. /// 调用await函数时, 是否带了所在函数传入的canceltoken参数
  208. /// </summary>
  209. private bool HasCancelTokenInAwait(InvocationExpressionSyntax awaitInvocationSyntax, IParameterSymbol cancelTokenSymbol,
  210. SyntaxNodeAnalysisContext context)
  211. {
  212. return awaitInvocationSyntax.ArgumentList.Arguments.Any(x => x.ToString() == cancelTokenSymbol.Name);
  213. }
  214. /// <summary>
  215. /// 表达式canceltoken参数是否为null或未赋值
  216. /// </summary>
  217. private bool CancelTokenArguIsNullOrNotSet(InvocationExpressionSyntax invocationExpressionSyntax, SyntaxNodeAnalysisContext context)
  218. {
  219. IMethodSymbol? methodSymbol = context.SemanticModel.GetSymbolInfo(invocationExpressionSyntax).Symbol as IMethodSymbol;
  220. if (methodSymbol == null)
  221. {
  222. return false;
  223. }
  224. // 忽略没有ETCancellationToken参数的表达式
  225. if (!methodSymbol.HasParameterType(Definition.ETCancellationToken, out IParameterSymbol? cancelTokenSYmbol))
  226. {
  227. return false;
  228. }
  229. var arguments = invocationExpressionSyntax.ArgumentList.Arguments;
  230. for (int i = 0; i < arguments.Count; i++)
  231. {
  232. ITypeSymbol? typeInfo = context.SemanticModel.GetTypeInfo(arguments[i].Expression).Type;
  233. if (typeInfo == null)
  234. {
  235. continue;
  236. }
  237. if (typeInfo.ToString() == Definition.ETCancellationToken)
  238. {
  239. return false;
  240. }
  241. }
  242. return true;
  243. }
  244. }
  245. }