/// <inheritdoc/> public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken) .ConfigureAwait(false); foreach (var diagnostic in context.Diagnostics) { if (syntaxRoot.TryFindNode <MemberDeclarationSyntax>(diagnostic, out var member) && semanticModel.TryGetSymbol(member, context.CancellationToken, out ISymbol memberSymbol)) { if (DisposeMethod.TryFindVirtualDispose(memberSymbol.ContainingType, semanticModel.Compilation, Search.TopLevel, out var disposeMethod) && disposeMethod.TrySingleDeclaration(context.CancellationToken, out MethodDeclarationSyntax disposeMethodDeclaration)) { context.RegisterDocumentEditorFix( "Dispose member.", (editor, cancellationToken) => DisposeInVirtualDisposeMethod(editor, memberSymbol, disposeMethodDeclaration, cancellationToken), diagnostic); } else if (DisposeMethod.TryFindIDisposableDispose(memberSymbol.ContainingType, semanticModel.Compilation, Search.TopLevel, out disposeMethod) && disposeMethod.TrySingleDeclaration(context.CancellationToken, out disposeMethodDeclaration)) { context.RegisterDocumentEditorFix( "Dispose member.", (editor, cancellationToken) => DisposeInDisposeMethod(editor, memberSymbol, disposeMethodDeclaration, cancellationToken), diagnostic); } } } }
private static bool ShouldCallBase(IMethodSymbol method, MethodDeclarationSyntax methodDeclaration, SyntaxNodeAnalysisContext context) { if (method.Parameters.TrySingle(out var parameter) && parameter.Type == KnownSymbol.Boolean && method.IsOverride && method.OverriddenMethod is IMethodSymbol overridden && !DisposeMethod.TryFindBaseCall(methodDeclaration, context.SemanticModel, context.CancellationToken, out _)) { if (overridden.DeclaringSyntaxReferences.Length == 0) { return(true); } else { using (var disposeWalker = DisposeWalker.Borrow(overridden, context.SemanticModel, context.CancellationToken)) { foreach (var disposeCall in disposeWalker) { if (DisposeCall.TryGetDisposed(disposeCall, context.SemanticModel, context.CancellationToken, out var disposed) && FieldOrProperty.TryCreate(disposed, out var fieldOrProperty) && !DisposableMember.IsDisposed(fieldOrProperty, method, context.SemanticModel, context.CancellationToken)) { return(true); } } } } } return(false); }
private static void HandleFieldOrProperty(SyntaxNodeAnalysisContext context, FieldOrProperty fieldOrProperty) { using (var assignedValues = AssignedValueWalker.Borrow(fieldOrProperty.Symbol, context.SemanticModel, context.CancellationToken)) { using (var recursive = RecursiveValues.Borrow(assignedValues, context.SemanticModel, context.CancellationToken)) { if (Disposable.IsAnyCreation(recursive, context.SemanticModel, context.CancellationToken).IsEither(Result.Yes, Result.AssumeYes)) { if (Disposable.IsAnyCachedOrInjected(recursive, context.SemanticModel, context.CancellationToken).IsEither(Result.Yes, Result.AssumeYes) || IsMutableFromOutside(fieldOrProperty)) { context.ReportDiagnostic(Diagnostic.Create(IDISP008DontMixInjectedAndCreatedForMember.Descriptor, context.Node.GetLocation())); } else if (context.Node.TryFirstAncestorOrSelf <TypeDeclarationSyntax>(out var typeDeclaration) && DisposableMember.IsDisposed(fieldOrProperty, typeDeclaration, context.SemanticModel, context.CancellationToken).IsEither(Result.No, Result.AssumeNo) && !TestFixture.IsAssignedAndDisposedInSetupAndTearDown(fieldOrProperty, typeDeclaration, context.SemanticModel, context.CancellationToken)) { context.ReportDiagnostic(Diagnostic.Create(IDISP002DisposeMember.Descriptor, context.Node.GetLocation())); if (!DisposeMethod.TryFindFirst(fieldOrProperty.ContainingType, context.Compilation, Search.TopLevel, out _) && !TestFixture.IsAssignedInSetUp(fieldOrProperty, typeDeclaration, context.SemanticModel, context.CancellationToken, out _)) { context.ReportDiagnostic(Diagnostic.Create(IDISP006ImplementIDisposable.Descriptor, context.Node.GetLocation())); } } } } } }
internal static DisposeWalker Borrow(ITypeSymbol type, SemanticModel semanticModel, CancellationToken cancellationToken) { if (type.IsAssignableTo(KnownSymbol.IDisposable, semanticModel.Compilation) && DisposeMethod.TryFindFirst(type, semanticModel.Compilation, Search.Recursive, out var disposeMethod)) { return(Borrow(disposeMethod, semanticModel, cancellationToken)); } return(Borrow(() => new DisposeWalker())); }
internal static DisposeWalker Borrow(INamedTypeSymbol type, SemanticModel semanticModel, CancellationToken cancellationToken) { if (type.IsAssignableTo(KnownSymbol.IDisposable, semanticModel.Compilation) && DisposeMethod.FindFirst(type, semanticModel.Compilation, Search.Recursive) is { } disposeMethod&& disposeMethod.TrySingleDeclaration(cancellationToken, out MethodDeclarationSyntax? declaration)) { return(BorrowAndVisit(declaration, SearchScope.Instance, type, semanticModel, () => new DisposeWalker(), cancellationToken)); } if (type.IsAssignableTo(KnownSymbol.IAsyncDisposable, semanticModel.Compilation) && type.TryFindFirstMethod(x => x is { Parameters: { Length: 0 } } && x == KnownSymbol.IAsyncDisposable.DisposeAsync, out var disposeAsync) &&
private static void DisposeInVirtualDisposeMethod(DocumentEditor editor, ISymbol memberSymbol, MethodDeclarationSyntax disposeMethodDeclaration, CancellationToken cancellationToken) { var disposeStatement = Snippet.DisposeStatement(memberSymbol, editor.SemanticModel, cancellationToken); if (TryFindIfDisposing(disposeMethodDeclaration, out var ifDisposing)) { if (ifDisposing.Statement is BlockSyntax block) { var statements = block.Statements.Add(disposeStatement); var newBlock = block.WithStatements(statements); editor.ReplaceNode(block, newBlock); } else if (ifDisposing.Statement is StatementSyntax statement) { editor.ReplaceNode( ifDisposing, ifDisposing.WithStatement(SyntaxFactory.Block(statement, disposeStatement))); } else { editor.ReplaceNode( ifDisposing, ifDisposing.WithStatement(SyntaxFactory.Block(disposeStatement))); } } else if (disposeMethodDeclaration.Body is BlockSyntax block) { ifDisposing = SyntaxFactory.IfStatement( SyntaxFactory.IdentifierName(disposeMethodDeclaration.ParameterList.Parameters[0].Identifier), SyntaxFactory.Block(disposeStatement)); if (DisposeMethod.TryFindBaseCall(disposeMethodDeclaration, editor.SemanticModel, cancellationToken, out var baseCall)) { if (baseCall.TryFirstAncestor(out ExpressionStatementSyntax expressionStatement)) { editor.InsertBefore(expressionStatement, ifDisposing); } } else { editor.ReplaceNode( block, block.AddStatements(ifDisposing)); } } }
private static void Handle(SyntaxNodeAnalysisContext context) { if (context.Node is ClassDeclarationSyntax classDeclaration && context.ContainingSymbol is INamedTypeSymbol { IsSealed : false } type&& type.IsAssignableTo(KnownSymbol.IDisposable, context.SemanticModel.Compilation) && DisposeMethod.Find(type, context.Compilation, Search.TopLevel) is { IsVirtual : false, IsAbstract : false, IsOverride : false } disposeMethod&& !HasDisposeDisposing(type)) { context.ReportDiagnostic( Diagnostic.Create( Descriptors.IDISP025SealDisposable, classDeclaration.Identifier.GetLocation(), additionalLocations : new[] { disposeMethod.Locations[0] })); } }
private static void Handle(SyntaxNodeAnalysisContext context) { if (!context.IsExcludedFromAnalysis() && context.Node is DestructorDeclarationSyntax methodDeclaration) { if (DisposeMethod.TryFindDisposeBoolCall(methodDeclaration, out _, out var isDisposing) && isDisposing.Expression?.IsKind(SyntaxKind.FalseLiteralExpression) != true) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP022DisposeFalse, isDisposing.GetLocation())); } using var walker = FinalizerContextWalker.Borrow(methodDeclaration, context.SemanticModel, context.CancellationToken); foreach (var node in walker.UsedReferenceTypes) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP023ReferenceTypeInFinalizerContext, node.GetLocation())); } } }
/// <inheritdoc/> public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); if (syntaxRoot == null) { return; } var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken) .ConfigureAwait(false); foreach (var diagnostic in context.Diagnostics) { if (!IsSupportedDiagnostic(diagnostic)) { continue; } var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start); if (string.IsNullOrEmpty(token.ValueText) || token.IsMissing) { continue; } var typeDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan) .FirstAncestorOrSelf <TypeDeclarationSyntax>(); if (diagnostic.Id == IDISP009IsIDisposable.DiagnosticId) { context.RegisterCodeFix( CodeAction.Create( "Add IDisposable interface", cancellationToken => AddInterfaceAsync( context, cancellationToken, typeDeclaration), nameof(ImplementIDisposableFix) + "add interface"), diagnostic); continue; } if (typeDeclaration is StructDeclarationSyntax structDeclaration) { context.RegisterCodeFix( CodeAction.Create( "Implement IDisposable.", cancellationToken => ImplementIDisposableStructAsync( context, cancellationToken, structDeclaration), nameof(ImplementIDisposableFix) + "Struct"), diagnostic); } else if (typeDeclaration is ClassDeclarationSyntax classDeclaration) { var type = semanticModel.GetDeclaredSymbolSafe(typeDeclaration, context.CancellationToken); if (Disposable.IsAssignableFrom(type, semanticModel.Compilation) && DisposeMethod.TryFindBaseVirtual(type, out var baseDispose)) { context.RegisterCodeFix( CodeAction.Create( "override Dispose(bool)", cancellationToken => OverrideDisposeAsync( context, classDeclaration, baseDispose, cancellationToken), nameof(ImplementIDisposableFix) + "override"), diagnostic); continue; } if (type.TryFindSingleMethodRecursive("Dispose", out var disposeMethod) && !disposeMethod.IsStatic && disposeMethod.ReturnsVoid && disposeMethod.Parameters.Length == 0) { continue; } if (type.TryFindFieldRecursive("disposed", out _) || type.TryFindFieldRecursive("_disposed", out _)) { return; } if (type.IsSealed) { context.RegisterCodeFix( CodeAction.Create( "Implement IDisposable.", cancellationToken => ImplementIDisposableSealedAsync( context, cancellationToken, classDeclaration), nameof(ImplementIDisposableFix) + "Sealed"), diagnostic); continue; } if (type.IsAbstract) { context.RegisterCodeFix( CodeAction.Create( "Implement IDisposable with virtual dispose method.", cancellationToken => ImplementIDisposableVirtualAsync( context, cancellationToken, classDeclaration), nameof(ImplementIDisposableFix) + "Virtual"), diagnostic); continue; } context.RegisterCodeFix( CodeAction.Create( "Implement IDisposable and make class sealed.", cancellationToken => ImplementIDisposableSealedAsync( context, cancellationToken, classDeclaration), nameof(ImplementIDisposableFix) + "Sealed"), diagnostic); context.RegisterCodeFix( CodeAction.Create( "Implement IDisposable with virtual dispose method.", cancellationToken => ImplementIDisposableVirtualAsync( context, cancellationToken, classDeclaration), nameof(ImplementIDisposableFix) + "Virtual"), diagnostic); } } }
internal static Result IsArgumentDisposedByReturnValue(ArgumentSyntax argument, SemanticModel semanticModel, CancellationToken cancellationToken, PooledSet <SyntaxNode> visited = null) { if (argument?.Parent is ArgumentListSyntax argumentList) { if (argumentList.Parent is InvocationExpressionSyntax invocation && semanticModel.GetSymbolSafe(invocation, cancellationToken) is IMethodSymbol method) { if (method.ContainingType.DeclaringSyntaxReferences.Length == 0) { return(method.ReturnsVoid || !IsAssignableFrom(method.ReturnType, semanticModel.Compilation) ? Result.No : Result.AssumeYes); } if (method.TryFindParameter(argument, out var parameter)) { return(CheckReturnValues(parameter, invocation, semanticModel, cancellationToken, visited)); } return(Result.Unknown); } if (argumentList.Parent is ObjectCreationExpressionSyntax || argumentList.Parent is ConstructorInitializerSyntax) { if (TryGetAssignedFieldOrProperty(argument, semanticModel, cancellationToken, out var member, out var ctor) && FieldOrProperty.TryCreate(member, out var fieldOrProperty)) { var initializer = argument.FirstAncestorOrSelf <ConstructorInitializerSyntax>(); if (initializer != null) { if (semanticModel.GetDeclaredSymbolSafe(initializer.Parent, cancellationToken) is IMethodSymbol chainedCtor && chainedCtor.ContainingType != member.ContainingType) { if (DisposeMethod.TryFindFirst(chainedCtor.ContainingType, semanticModel.Compilation, Search.TopLevel, out var disposeMethod)) { return(DisposableMember.IsDisposed(fieldOrProperty, disposeMethod, semanticModel, cancellationToken) ? Result.Yes : Result.No); } } } return(DisposableMember.IsDisposed(fieldOrProperty, ctor.ContainingType, semanticModel, cancellationToken)); } if (ctor == null) { return(Result.AssumeYes); } if (ctor.ContainingType.DeclaringSyntaxReferences.Length == 0) { return(IsAssignableFrom(ctor.ContainingType, semanticModel.Compilation) ? Result.AssumeYes : Result.No); } if (ctor.ContainingType.IsAssignableTo(KnownSymbol.NinjectStandardKernel, semanticModel.Compilation)) { return(Result.Yes); } return(Result.No); } } return(Result.Unknown); }
private static void Handle(SyntaxNodeAnalysisContext context) { if (!context.IsExcludedFromAnalysis() && context.ContainingSymbol is IMethodSymbol method && context.Node is MethodDeclarationSyntax methodDeclaration && method.Name == "Dispose" && method.ReturnsVoid) { if (method.Parameters.Length == 0 && !method.IsStatic && method.DeclaredAccessibility == Accessibility.Public && method.ReturnsVoid && method.OverriddenMethod == null && method.GetAttributes().Length == 0) { if (!method.ExplicitInterfaceImplementations.Any() && !IsInterfaceImplementation(method)) { context.ReportDiagnostic(Diagnostic.Create(IDISP009IsIDisposable.Descriptor, methodDeclaration.Identifier.GetLocation())); } if (DisposeMethod.TryFindSuppressFinalizeCall(methodDeclaration, context.SemanticModel, context.CancellationToken, out var suppressFinalize)) { if (suppressFinalize.ArgumentList is ArgumentListSyntax argumentList && argumentList.Arguments.TrySingle(out var argument) && !argument.Expression.IsKind(SyntaxKind.ThisExpression)) { context.ReportDiagnostic(Diagnostic.Create(IDISP020SuppressFinalizeThis.Descriptor, argument.GetLocation())); } } else if (method.ContainingType.TryFindFirstMethod(x => x.MethodKind == MethodKind.Destructor, out _)) { context.ReportDiagnostic(Diagnostic.Create(IDISP018CallSuppressFinalizeWhenFinalizer.Descriptor, methodDeclaration.Identifier.GetLocation())); } else if (method.ContainingType.TryFindFirstMethod(x => DisposeMethod.IsVirtualDispose(x), out _)) { context.ReportDiagnostic(Diagnostic.Create(IDISP019CallSuppressFinalizeWhenVirtualDispose.Descriptor, methodDeclaration.Identifier.GetLocation())); } if (DisposeMethod.TryFindDisposeBoolCall(methodDeclaration, context.SemanticModel, context.CancellationToken, out _, out var isDisposing) && isDisposing.Expression?.IsKind(SyntaxKind.TrueLiteralExpression) != true) { context.ReportDiagnostic(Diagnostic.Create(IDISP021DisposeTrue.Descriptor, isDisposing.GetLocation())); } } if (method.Parameters.TrySingle(out var parameter) && parameter.Type == KnownSymbol.Boolean) { if (ShouldCallBase(method, methodDeclaration, context)) { context.ReportDiagnostic(Diagnostic.Create(IDISP010CallBaseDispose.Descriptor, methodDeclaration.Identifier.GetLocation(), parameter.Name)); } using (var walker = FinalizerContextWalker.Borrow(methodDeclaration, context.SemanticModel, context.CancellationToken)) { foreach (var node in walker.UsedReferenceTypes) { context.ReportDiagnostic(Diagnostic.Create(IDISP023ReferenceTypeInFinalizerContext.Descriptor, node.GetLocation())); } } } } }