private bool AnalyzeMemberWithinContext(ITypeSymbol type, ISymbol?symbol, SyntaxNodeAnalysisContext context, Location?focusDiagnosticOn = null) { if (type is null) { throw new ArgumentNullException(nameof(type)); } bool requiresUIThread = (type.TypeKind == TypeKind.Interface || type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) && this.MembersRequiringMainThread.Contains(type, symbol); if (requiresUIThread) { ThreadingContext threadingContext = ThreadingContext.Unknown; SyntaxNode? methodDeclaration = context.Node.FirstAncestorOrSelf <SyntaxNode>(n => CSharpCommonInterest.MethodSyntaxKinds.Contains(n.Kind())); if (methodDeclaration is object) { threadingContext = this.methodDeclarationNodes.GetValueOrDefault(methodDeclaration); } if (threadingContext != ThreadingContext.MainThread) { CSharpUtils.ContainingFunctionData function = CSharpUtils.GetContainingFunction((CSharpSyntaxNode)context.Node); Location location = focusDiagnosticOn ?? context.Node.GetLocation(); DiagnosticDescriptor?descriptor = function.IsAsync ? DescriptorAsync : DescriptorSync; var formattingArgs = function.IsAsync ? new object[] { type.Name } : new object[] { type.Name, this.MainThreadAssertingMethods.FirstOrDefault() }; context.ReportDiagnostic(Diagnostic.Create(descriptor, location, this.DiagnosticProperties, formattingArgs)); return(true); } } return(false); }
private void AddToCallerCalleeMap(OperationAnalysisContext context, Dictionary <IMethodSymbol, List <CallInfo> > callerToCalleeMap) { if (CSharpUtils.IsWithinNameOf(context.Operation.Syntax)) { return; } IMethodSymbol?GetPropertyAccessor(IPropertySymbol?propertySymbol) { if (propertySymbol is object) { return(CSharpUtils.IsOnLeftHandOfAssignment(context.Operation.Syntax) ? propertySymbol.SetMethod : propertySymbol.GetMethod); } return(null); } ISymbol? targetMethod = null; SyntaxNode locationToBlame = context.Operation.Syntax; switch (context.Operation) { case IInvocationOperation invocationOperation: targetMethod = invocationOperation.TargetMethod; locationToBlame = this.languageUtils.IsolateMethodName(invocationOperation); break; case IPropertyReferenceOperation propertyReference: targetMethod = GetPropertyAccessor(propertyReference.Property); break; case IEventAssignmentOperation eventAssignmentOperation: IOperation eventReferenceOp = eventAssignmentOperation.EventReference; if (eventReferenceOp is IEventReferenceOperation eventReference) { targetMethod = eventAssignmentOperation.Adds ? eventReference.Event.AddMethod : eventReference.Event.RemoveMethod; locationToBlame = eventReference.Syntax; } break; } if (context.ContainingSymbol is IMethodSymbol caller && targetMethod is IMethodSymbol callee) { lock (callerToCalleeMap) { if (!callerToCalleeMap.TryGetValue(caller, out List <CallInfo> callees)) { callerToCalleeMap[caller] = callees = new List <CallInfo>(); } callees.Add(new CallInfo(methodSymbol: callee, invocationSyntax: locationToBlame)); } } }
internal static void InspectMemberAccess( SyntaxNodeAnalysisContext context, MemberAccessExpressionSyntax?memberAccessSyntax, DiagnosticDescriptor descriptor, IEnumerable <CommonInterest.SyncBlockingMethod> problematicMethods, bool ignoreIfInsideAnonymousDelegate = false) { if (descriptor is null) { throw new ArgumentNullException(nameof(descriptor)); } if (memberAccessSyntax is null) { return; } if (ShouldIgnoreContext(context)) { return; } if (ignoreIfInsideAnonymousDelegate && context.Node.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>() is object) { // We do not analyze JTF.Run inside anonymous functions because // they are so often used as callbacks where the signature is constrained. return; } if (CSharpUtils.IsWithinNameOf(context.Node as ExpressionSyntax)) { // We do not consider arguments to nameof( ) because they do not represent invocations of code. return; } ITypeSymbol?typeReceiver = context.SemanticModel.GetTypeInfo(memberAccessSyntax.Expression).Type; if (typeReceiver is object) { foreach (CommonInterest.SyncBlockingMethod item in problematicMethods) { if (memberAccessSyntax.Name.Identifier.Text == item.Method.Name && typeReceiver.Name == item.Method.ContainingType.Name && typeReceiver.BelongsToNamespace(item.Method.ContainingType.Namespace)) { if (HasTaskCompleted(context, memberAccessSyntax)) { return; } Location?location = memberAccessSyntax.Name.GetLocation(); context.ReportDiagnostic(Diagnostic.Create(descriptor, location)); } } } }
internal void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { if (IsInTaskReturningMethodOrDelegate(context)) { var invocationExpressionSyntax = (InvocationExpressionSyntax)context.Node; var memberAccessSyntax = invocationExpressionSyntax.Expression as MemberAccessExpressionSyntax; if (InspectMemberAccess(context, memberAccessSyntax, CommonInterest.SyncBlockingMethods)) { // Don't return double-diagnostics. return; } MethodDeclarationSyntax invocationDeclaringMethod = invocationExpressionSyntax.FirstAncestorOrSelf <MethodDeclarationSyntax>(); // Also consider all method calls to check for Async-suffixed alternatives. ExpressionSyntax invokedMethodName = CSharpUtils.IsolateMethodName(invocationExpressionSyntax); var symbolInfo = context.SemanticModel.GetSymbolInfo(invocationExpressionSyntax, context.CancellationToken); var methodSymbol = symbolInfo.Symbol as IMethodSymbol; if (methodSymbol != null && !methodSymbol.Name.EndsWith(VSTHRD200UseAsyncNamingConventionAnalyzer.MandatoryAsyncSuffix) && !(methodSymbol.ReturnType?.Name == nameof(Task) && methodSymbol.ReturnType.BelongsToNamespace(Namespaces.SystemThreadingTasks))) { string asyncMethodName = methodSymbol.Name + VSTHRD200UseAsyncNamingConventionAnalyzer.MandatoryAsyncSuffix; var symbols = context.SemanticModel.LookupSymbols( invocationExpressionSyntax.Expression.GetLocation().SourceSpan.Start, methodSymbol.ContainingType, asyncMethodName, includeReducedExtensionMethods: true); foreach (var s in symbols) { if (s is IMethodSymbol m && !m.IsObsolete() && HasSupersetOfParameterTypes(m, methodSymbol) && m.Name != invocationDeclaringMethod?.Identifier.Text && Utils.HasAsyncCompatibleReturnType(m)) { // An async alternative exists. var properties = ImmutableDictionary <string, string> .Empty .Add(AsyncMethodKeyName, asyncMethodName); Diagnostic diagnostic = Diagnostic.Create( Descriptor, invokedMethodName.GetLocation(), properties, invokedMethodName.ToString(), asyncMethodName); context.ReportDiagnostic(diagnostic); return; } } } } }
private static void AddToCallerCalleeMap(SyntaxNodeAnalysisContext context, Dictionary <IMethodSymbol, List <CallInfo> > callerToCalleeMap) { if (CSharpUtils.IsWithinNameOf(context.Node)) { return; } IMethodSymbol?GetPropertyAccessor(IPropertySymbol?propertySymbol) { if (propertySymbol is object) { return(CSharpUtils.IsOnLeftHandOfAssignment(context.Node) ? propertySymbol.SetMethod : propertySymbol.GetMethod); } return(null); } ISymbol? targetMethod = null; SyntaxNode locationToBlame = context.Node; switch (context.Node) { case InvocationExpressionSyntax invocationExpressionSyntax: targetMethod = context.SemanticModel.GetSymbolInfo(invocationExpressionSyntax.Expression, context.CancellationToken).Symbol; locationToBlame = invocationExpressionSyntax.Expression; break; case MemberAccessExpressionSyntax memberAccessExpressionSyntax: targetMethod = GetPropertyAccessor(context.SemanticModel.GetSymbolInfo(memberAccessExpressionSyntax.Name, context.CancellationToken).Symbol as IPropertySymbol); break; case IdentifierNameSyntax identifierNameSyntax: targetMethod = GetPropertyAccessor(context.SemanticModel.GetSymbolInfo(identifierNameSyntax, context.CancellationToken).Symbol as IPropertySymbol); break; } if (context.ContainingSymbol is IMethodSymbol caller && targetMethod is IMethodSymbol callee) { lock (callerToCalleeMap) { if (!callerToCalleeMap.TryGetValue(caller, out List <CallInfo> callees)) { callerToCalleeMap[caller] = callees = new List <CallInfo>(); } callees.Add(new CallInfo(methodSymbol: callee, invocationSyntax: locationToBlame)); } } }
private void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { var invocationExpressionSyntax = (InvocationExpressionSyntax)context.Node; ExpressionSyntax invokedMethodName = CSharpUtils.IsolateMethodName(invocationExpressionSyntax); var argList = invocationExpressionSyntax.ArgumentList; var symbolInfo = context.SemanticModel.GetSymbolInfo(invocationExpressionSyntax, context.CancellationToken); var methodSymbol = symbolInfo.Symbol as IMethodSymbol; // nameof(X) has no method symbol. So skip over such. if (methodSymbol != null) { var otherOverloads = methodSymbol.ContainingType.GetMembers(methodSymbol.Name).OfType <IMethodSymbol>(); AnalyzeCall(context, invokedMethodName.GetLocation(), argList, methodSymbol, otherOverloads); } }
internal void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { var invocation = (InvocationExpressionSyntax)context.Node; // Only consider invocations that are direct statements. Otherwise, we assume their // result is awaited, assigned, or otherwise consumed. if (invocation.Parent is ExpressionStatementSyntax || invocation.Parent is ConditionalAccessExpressionSyntax) { var methodSymbol = context.SemanticModel.GetSymbolInfo(context.Node).Symbol as IMethodSymbol; if (this.IsAwaitableType(methodSymbol?.ReturnType, context.Compilation, context.CancellationToken)) { if (!CSharpUtils.GetContainingFunction(invocation).IsAsync) { Location?location = (CSharpUtils.IsolateMethodName(invocation) ?? invocation.Expression).GetLocation(); context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } } } }
private void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { var invocation = (InvocationExpressionSyntax)context.Node; // Only consider invocations that are direct statements. Otherwise, we assume their // result is awaited, assigned, or otherwise consumed. if (invocation.Parent?.GetType().Equals(typeof(ExpressionStatementSyntax)) ?? false) { var methodSymbol = context.SemanticModel.GetSymbolInfo(context.Node).Symbol as IMethodSymbol; var returnedSymbol = methodSymbol?.ReturnType; if (returnedSymbol?.Name == Types.Task.TypeName && returnedSymbol.BelongsToNamespace(Types.Task.Namespace)) { if (!CSharpUtils.GetContainingFunction(invocation).IsAsync) { var location = (CSharpUtils.IsolateMethodName(invocation) ?? invocation.Expression).GetLocation(); context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } } } }
private void InspectMemberAccess(SyntaxNodeAnalysisContext context, MemberAccessExpressionSyntax?memberAccessSyntax, IEnumerable <CommonInterest.SyncBlockingMethod> problematicMethods) { if (memberAccessSyntax is null) { return; } if (this.diagnosticReported) { // Don't report more than once per method. return; } if (context.Node.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>() is object) { // We do not analyze JTF.Run inside anonymous functions because // they are so often used as callbacks where the signature is constrained. return; } if (CSharpUtils.IsWithinNameOf(context.Node as ExpressionSyntax)) { // We do not consider arguments to nameof( ) because they do not represent invocations of code. return; } ISymbol?invokedMember = context.SemanticModel.GetSymbolInfo(memberAccessSyntax, context.CancellationToken).Symbol; if (invokedMember is object) { foreach (CommonInterest.SyncBlockingMethod item in problematicMethods) { if (item.Method.IsMatch(invokedMember)) { Location?location = memberAccessSyntax.Name.GetLocation(); context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); this.diagnosticReported = true; } } } }
public override async Task RegisterCodeFixesAsync(CodeFixContext context) { Diagnostic?diagnostic = context.Diagnostics.First(); SyntaxNode?root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var syntaxNode = (ExpressionSyntax)root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); CSharpUtils.ContainingFunctionData container = CSharpUtils.GetContainingFunction(syntaxNode); if (container.BlockOrExpression is null) { return; } SemanticModel?semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); ISymbol?enclosingSymbol = semanticModel.GetEnclosingSymbol(diagnostic.Location.SourceSpan.Start, context.CancellationToken); if (enclosingSymbol is null) { return; } bool convertToAsync = !container.IsAsync && Utils.HasAsyncCompatibleReturnType(enclosingSymbol as IMethodSymbol); if (convertToAsync) { // We don't support this yet, and we don't want to take the sync method path in this case. // The user will have to fix this themselves. return; } Regex lookupKey = (container.IsAsync || convertToAsync) ? CommonInterest.FileNamePatternForMethodsThatSwitchToMainThread : CommonInterest.FileNamePatternForMethodsThatAssertMainThread; string[] options = diagnostic.Properties[lookupKey.ToString()].Split('\n'); if (options.Length > 0) { // For any symbol lookups, we want to consider the position of the very first statement in the block. int positionForLookup = container.BlockOrExpression.GetLocation().SourceSpan.Start + 1; Lazy <ISymbol> cancellationTokenSymbol = new Lazy <ISymbol>(() => Utils.FindCancellationToken(semanticModel, positionForLookup, context.CancellationToken).FirstOrDefault()); foreach (var option in options) { // We're looking for methods that either require no parameters, // or (if we have one to give) that have just one parameter that is a CancellationToken. IMethodSymbol?proposedMethod = Utils.FindMethodGroup(semanticModel, option) .FirstOrDefault(m => !m.Parameters.Any(p => !p.HasExplicitDefaultValue) || (cancellationTokenSymbol.Value is object && m.Parameters.Length == 1 && Utils.IsCancellationTokenParameter(m.Parameters[0]))); if (proposedMethod is null) { // We can't find it, so don't offer to use it. continue; } if (proposedMethod.IsStatic) { OfferFix(option); } else { foreach (Tuple <bool, ISymbol>?candidate in Utils.FindInstanceOf(proposedMethod.ContainingType, semanticModel, positionForLookup, context.CancellationToken)) { if (candidate.Item1) { OfferFix($"{candidate.Item2.Name}.{proposedMethod.Name}"); } else { OfferFix($"{candidate.Item2.ContainingNamespace}.{candidate.Item2.ContainingType.Name}.{candidate.Item2.Name}.{proposedMethod.Name}"); } } } void OfferFix(string fullyQualifiedMethod) { context.RegisterCodeFix(CodeAction.Create($"Add call to {fullyQualifiedMethod}", ct => Fix(fullyQualifiedMethod, proposedMethod, cancellationTokenSymbol), fullyQualifiedMethod), context.Diagnostics); } } } Task <Document> Fix(string fullyQualifiedMethod, IMethodSymbol methodSymbol, Lazy <ISymbol> cancellationTokenSymbol) { int typeAndMethodDelimiterIndex = fullyQualifiedMethod.LastIndexOf('.'); IdentifierNameSyntax methodName = SyntaxFactory.IdentifierName(fullyQualifiedMethod.Substring(typeAndMethodDelimiterIndex + 1)); ExpressionSyntax invokedMethod = CSharpUtils.MemberAccess(fullyQualifiedMethod.Substring(0, typeAndMethodDelimiterIndex).Split('.'), methodName); InvocationExpressionSyntax?invocationExpression = SyntaxFactory.InvocationExpression(invokedMethod); IParameterSymbol? cancellationTokenParameter = methodSymbol.Parameters.FirstOrDefault(Utils.IsCancellationTokenParameter); if (cancellationTokenParameter is object && cancellationTokenSymbol.Value is object) { ArgumentSyntax?arg = SyntaxFactory.Argument(SyntaxFactory.IdentifierName(cancellationTokenSymbol.Value.Name)); if (methodSymbol.Parameters.IndexOf(cancellationTokenParameter) > 0) { arg = arg.WithNameColon(SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(cancellationTokenParameter.Name))); } invocationExpression = invocationExpression.AddArgumentListArguments(arg); } ExpressionSyntax? awaitExpression = container.IsAsync ? SyntaxFactory.AwaitExpression(invocationExpression) : null; ExpressionStatementSyntax?addedStatement = SyntaxFactory.ExpressionStatement(awaitExpression ?? invocationExpression) .WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation); var initialBlockSyntax = container.BlockOrExpression as BlockSyntax; if (initialBlockSyntax is null) { SyntaxToken openBrace = SyntaxFactory.Token(SyntaxFactory.TriviaList(), SyntaxKind.OpenBraceToken, SyntaxFactory.TriviaList(SyntaxFactory.EndOfLine("\r\n"))); SyntaxToken closeBrace = SyntaxFactory.Token(SyntaxKind.CloseBraceToken); SyntaxList <StatementSyntax> statementList = SyntaxFactory.List <StatementSyntax>(new[] { SyntaxFactory.ReturnStatement((ExpressionSyntax)container.BlockOrExpression) }); initialBlockSyntax = SyntaxFactory.Block(openBrace, statementList, closeBrace) .WithAdditionalAnnotations(Formatter.Annotation); } BlockSyntax?newBlock = initialBlockSyntax.WithStatements(initialBlockSyntax.Statements.Insert(0, addedStatement)); return(Task.FromResult(context.Document.WithSyntaxRoot(root.ReplaceNode(container.BlockOrExpression.Parent, container.BodyReplacement(newBlock))))); } }
public override async Task RegisterCodeFixesAsync(CodeFixContext context) { foreach (var diagnostic in context.Diagnostics) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); ExpressionSyntax?syntaxNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) as ExpressionSyntax; if (syntaxNode is null) { continue; } var container = CSharpUtils.GetContainingFunction(syntaxNode); if (container.BlockOrExpression == null) { return; } if (!container.IsAsync) { if (!(container.Function is MethodDeclarationSyntax || container.Function is AnonymousFunctionExpressionSyntax)) { // We don't support converting whatever this is into an async method. return; } } var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); var enclosingSymbol = semanticModel.GetEnclosingSymbol(diagnostic.Location.SourceSpan.Start, context.CancellationToken); if (enclosingSymbol == null) { return; } var hasReturnValue = ((enclosingSymbol as IMethodSymbol)?.ReturnType as INamedTypeSymbol)?.IsGenericType ?? false; var options = await CommonFixes.ReadMethodsAsync(context, CommonInterest.FileNamePatternForMethodsThatSwitchToMainThread, context.CancellationToken); int positionForLookup = diagnostic.Location.SourceSpan.Start; ISymbol cancellationTokenSymbol = Utils.FindCancellationToken(semanticModel, positionForLookup, context.CancellationToken).FirstOrDefault(); foreach (var option in options) { // We're looking for methods that either require no parameters, // or (if we have one to give) that have just one parameter that is a CancellationToken. var proposedMethod = Utils.FindMethodGroup(semanticModel, option) .FirstOrDefault(m => !m.Parameters.Any(p => !p.HasExplicitDefaultValue) || (cancellationTokenSymbol != null && m.Parameters.Length == 1 && Utils.IsCancellationTokenParameter(m.Parameters[0]))); if (proposedMethod == null) { // We can't find it, so don't offer to use it. continue; } if (proposedMethod.IsStatic) { OfferFix(option.ToString()); } else { foreach (var candidate in Utils.FindInstanceOf(proposedMethod.ContainingType, semanticModel, positionForLookup, context.CancellationToken)) { if (candidate.Item1) { OfferFix($"{candidate.Item2.Name}.{proposedMethod.Name}"); } else { OfferFix($"{candidate.Item2.ContainingNamespace}.{candidate.Item2.ContainingType.Name}.{candidate.Item2.Name}.{proposedMethod.Name}"); } } } void OfferFix(string fullyQualifiedMethod) { context.RegisterCodeFix(CodeAction.Create($"Use 'await {fullyQualifiedMethod}'", ct => Fix(fullyQualifiedMethod, proposedMethod, hasReturnValue, ct), fullyQualifiedMethod), context.Diagnostics); } } async Task <Solution> Fix(string fullyQualifiedMethod, IMethodSymbol methodSymbol, bool hasReturnValue, CancellationToken cancellationToken) { var assertionStatementToRemove = syntaxNode !.FirstAncestorOrSelf <StatementSyntax>(); int typeAndMethodDelimiterIndex = fullyQualifiedMethod.LastIndexOf('.'); IdentifierNameSyntax methodName = SyntaxFactory.IdentifierName(fullyQualifiedMethod.Substring(typeAndMethodDelimiterIndex + 1)); ExpressionSyntax invokedMethod = CSharpUtils.MemberAccess(fullyQualifiedMethod.Substring(0, typeAndMethodDelimiterIndex).Split('.'), methodName) .WithAdditionalAnnotations(Simplifier.Annotation); var invocationExpression = SyntaxFactory.InvocationExpression(invokedMethod); var cancellationTokenParameter = methodSymbol.Parameters.FirstOrDefault(Utils.IsCancellationTokenParameter); if (cancellationTokenParameter != null && cancellationTokenSymbol != null) { var arg = SyntaxFactory.Argument(SyntaxFactory.IdentifierName(cancellationTokenSymbol.Name)); if (methodSymbol.Parameters.IndexOf(cancellationTokenParameter) > 0) { arg = arg.WithNameColon(SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(cancellationTokenParameter.Name))); } invocationExpression = invocationExpression.AddArgumentListArguments(arg); } ExpressionSyntax awaitExpression = SyntaxFactory.AwaitExpression(invocationExpression); var addedStatement = SyntaxFactory.ExpressionStatement(awaitExpression) .WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation); var methodAnnotation = new SyntaxAnnotation(); CSharpSyntaxNode methodSyntax = container.Function.ReplaceNode(assertionStatementToRemove, addedStatement) .WithAdditionalAnnotations(methodAnnotation); Document newDocument = context.Document.WithSyntaxRoot(root.ReplaceNode(container.Function, methodSyntax)); var newSyntaxRoot = await newDocument.GetSyntaxRootAsync(cancellationToken); methodSyntax = (CSharpSyntaxNode)newSyntaxRoot.GetAnnotatedNodes(methodAnnotation).Single(); if (!container.IsAsync) { switch (methodSyntax) { case AnonymousFunctionExpressionSyntax anonFunc: semanticModel = await newDocument.GetSemanticModelAsync(cancellationToken); methodSyntax = FixUtils.MakeMethodAsync(anonFunc, hasReturnValue, semanticModel, cancellationToken); newDocument = newDocument.WithSyntaxRoot(newSyntaxRoot.ReplaceNode(anonFunc, methodSyntax)); break; case MethodDeclarationSyntax methodDecl: (newDocument, methodSyntax) = await FixUtils.MakeMethodAsync(methodDecl, newDocument, cancellationToken); break; } } return(newDocument.Project.Solution); } } }