internal static bool IsDisposedAfter(ISymbol local, ExpressionSyntax location, SemanticModel semanticModel, CancellationToken cancellationToken) { if (location.FirstAncestorOrSelf <MemberDeclarationSyntax>() is MemberDeclarationSyntax scope) { using (var walker = InvocationWalker.Borrow(scope)) { foreach (var invocation in walker.Invocations) { if (location.IsExecutedBefore(invocation).IsEither(ExecutedBefore.Maybe, ExecutedBefore.Yes) && DisposeCall.IsDisposing(invocation, local, semanticModel, cancellationToken)) { return(true); } } } using (var walker = UsingStatementWalker.Borrow(scope)) { foreach (var usingStatement in walker.UsingStatements) { if (location.IsExecutedBefore(usingStatement) == ExecutedBefore.Yes && usingStatement.Expression is IdentifierNameSyntax identifierName && identifierName.Identifier.ValueText == local.Name) { return(true); } } } } return(false); }
private static bool IsAddedToFieldOrProperty(ILocalSymbol symbol, BlockSyntax block, SemanticModel semanticModel, CancellationToken cancellationToken) { using (var pooledInvocations = InvocationWalker.Borrow(block)) { foreach (var invocation in pooledInvocations.Invocations) { var method = semanticModel.GetSymbolSafe(invocation, cancellationToken) as IMethodSymbol; if (method?.Name == "Add") { using (var nameWalker = IdentifierNameWalker.Borrow(invocation.ArgumentList)) { foreach (var identifierName in nameWalker.IdentifierNames) { var argSymbol = semanticModel.GetSymbolSafe(identifierName, cancellationToken); if (symbol.Equals(argSymbol)) { return(true); } } } } } } return(false); }
internal static bool TryFindBaseCall(MethodDeclarationSyntax virtualDispose, SemanticModel semanticModel, CancellationToken cancellationToken, out InvocationExpressionSyntax baseCall) { if (virtualDispose.ParameterList is ParameterListSyntax parameterList && parameterList.Parameters.TrySingle(out var parameter)) { using (var invocations = InvocationWalker.Borrow(virtualDispose)) { foreach (var invocation in invocations) { if (invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression is BaseExpressionSyntax && invocation.TryGetMethodName(out var name) && name == virtualDispose.Identifier.ValueText && invocation.ArgumentList is ArgumentListSyntax argumentList && argumentList.Arguments.TrySingle(out var argument) && argument.Expression is IdentifierNameSyntax identifierName && identifierName.Identifier.ValueText == parameter.Identifier.ValueText && semanticModel.TryGetSymbol(invocation, cancellationToken, out var target) && semanticModel.TryGetSymbol(virtualDispose, cancellationToken, out var method) && method.IsOverride && method.OverriddenMethod is IMethodSymbol overridden && target.Equals(overridden)) { baseCall = invocation; return(true); } } } } baseCall = null; return(false); }
private static void HandleDisposeMethod(SyntaxNodeAnalysisContext context) { if (context.IsExcludedFromAnalysis()) { return; } if (context.ContainingSymbol is IMethodSymbol method && method.IsOverride && method.Name == "Dispose") { var overridden = method.OverriddenMethod; if (overridden == null) { return; } using (var invocations = InvocationWalker.Borrow(context.Node)) { foreach (var invocation in invocations) { if ( SymbolComparer.Equals( context.SemanticModel.GetSymbolSafe(invocation, context.CancellationToken), overridden)) { return; } } } if (overridden.DeclaringSyntaxReferences.Length == 0) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation())); return; } using ( var disposeWalker = Disposable.DisposeWalker.Borrow(overridden, context.SemanticModel, context.CancellationToken)) { foreach (var disposeCall in disposeWalker) { if (Disposable.TryGetDisposedRootMember(disposeCall, context.SemanticModel, context.CancellationToken, out ExpressionSyntax disposed)) { var member = context.SemanticModel.GetSymbolSafe(disposed, context.CancellationToken); if ( !Disposable.IsMemberDisposed(member, method, context.SemanticModel, context.CancellationToken)) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation())); return; } } } } } }
private static bool IsDisposedAfter(ISymbol symbol, ExpressionSyntax assignment, SemanticModel semanticModel, CancellationToken cancellationToken) { using (var pooled = InvocationWalker.Borrow(assignment.FirstAncestorOrSelf <MemberDeclarationSyntax>())) { foreach (var invocation in pooled.Invocations) { if (!IsAfter(invocation, assignment)) { continue; } if (Disposable.IsDisposing(invocation, symbol, semanticModel, cancellationToken)) { return(true); } } } return(false); }
internal static bool TryFindSuppressFinalizeCall(MethodDeclarationSyntax disposeMethod, SemanticModel semanticModel, CancellationToken cancellationToken, out InvocationExpressionSyntax suppressCall) { using (var invocations = InvocationWalker.Borrow(disposeMethod)) { foreach (var candidate in invocations) { if (candidate.ArgumentList is ArgumentListSyntax argumentList && argumentList.Arguments.Count == 1 && candidate.TryGetMethodName(out var name) && name == "SuppressFinalize" && semanticModel.TryGetSymbol(candidate, KnownSymbol.GC.SuppressFinalize, cancellationToken, out var target)) { suppressCall = candidate; return(true); } } } suppressCall = null; return(false); }
internal static bool IsDisposedBefore(ISymbol symbol, ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken) { if (TryGetScope(expression, out var block)) { using var walker = InvocationWalker.Borrow(block); foreach (var invocation in walker.Invocations) { if (invocation.IsExecutedBefore(expression) == ExecutedBefore.No) { continue; } if (DisposeCall.IsDisposing(invocation, symbol, semanticModel, cancellationToken) && !IsReassignedAfter(block, invocation)) { return(true); } } } if (expression is AssignmentExpressionSyntax { Left : { } left } &&
internal static bool TryFindDisposeBoolCall(BaseMethodDeclarationSyntax disposeMethod, SemanticModel semanticModel, CancellationToken cancellationToken, out InvocationExpressionSyntax suppressCall, out ArgumentSyntax argument) { using (var invocations = InvocationWalker.Borrow(disposeMethod)) { foreach (var candidate in invocations) { if (candidate.ArgumentList is ArgumentListSyntax argumentList && argumentList.Arguments.TrySingle(out argument) && argument.Expression is ExpressionSyntax expression && expression.IsEither(SyntaxKind.TrueLiteralExpression, SyntaxKind.FalseLiteralExpression) && candidate.TryGetMethodName(out var name) && name == "Dispose") { suppressCall = candidate; return(true); } } } suppressCall = null; argument = null; return(false); }
private static bool IsDisposedBefore(ISymbol symbol, ExpressionSyntax assignment, SemanticModel semanticModel, CancellationToken cancellationToken) { using (var pooled = InvocationWalker.Borrow(assignment.FirstAncestorOrSelf <MemberDeclarationSyntax>())) { foreach (var invocation in pooled.Invocations) { if (invocation.IsBeforeInScope(assignment) != Result.Yes) { continue; } var invokedSymbol = semanticModel.GetSymbolSafe(invocation, cancellationToken); if (invokedSymbol?.Name != "Dispose") { continue; } var statement = invocation.FirstAncestorOrSelf <StatementSyntax>(); if (statement != null) { using (var names = IdentifierNameWalker.Borrow(statement)) { foreach (var identifierName in names.IdentifierNames) { var otherSymbol = semanticModel.GetSymbolSafe(identifierName, cancellationToken); if (symbol.Equals(otherSymbol)) { return(true); } } } } } } return(false); }
internal static bool IsDisposedBefore(ISymbol symbol, ExpressionSyntax assignment, SemanticModel semanticModel, CancellationToken cancellationToken) { bool IsDisposing(InvocationExpressionSyntax invocation, ISymbol current) { if (invocation.TryGetInvokedMethodName(out var name) && name != "Dispose") { return(false); } var invokedSymbol = semanticModel.GetSymbolSafe(invocation, cancellationToken); if (invokedSymbol?.Name != "Dispose") { return(false); } var statement = invocation.FirstAncestorOrSelf <StatementSyntax>(); if (statement != null) { using (var names = IdentifierNameWalker.Borrow(statement)) { foreach (var identifierName in names.IdentifierNames) { if (identifierName.Identifier.ValueText == current.Name && SymbolComparer.Equals(current, semanticModel.GetSymbolSafe(identifierName, cancellationToken))) { return(true); } } } } return(false); } bool TryGetScope(SyntaxNode node, out BlockSyntax result) { result = null; if (node.FirstAncestor <AnonymousFunctionExpressionSyntax>() is AnonymousFunctionExpressionSyntax lambda) { result = lambda.Body as BlockSyntax; } else if (node.FirstAncestor <AccessorDeclarationSyntax>() is AccessorDeclarationSyntax accessor) { result = accessor.Body; } else if (node.FirstAncestor <BaseMethodDeclarationSyntax>() is BaseMethodDeclarationSyntax method) { result = method.Body; } return(result != null); } if (TryGetScope(assignment, out var block)) { using (var walker = InvocationWalker.Borrow(block)) { foreach (var invocation in walker.Invocations) { if (invocation.IsBeforeInScope(assignment) != Result.Yes) { continue; } if (IsDisposing(invocation, symbol)) { return(true); } } } } if (assignment is AssignmentExpressionSyntax assignmentExpression && semanticModel.GetSymbolSafe(assignmentExpression.Left, cancellationToken) is IPropertySymbol property && property.TryGetSetter(cancellationToken, out var setter)) { using (var pooled = InvocationWalker.Borrow(setter)) { foreach (var invocation in pooled.Invocations) { if (IsDisposing(invocation, symbol) || IsDisposing(invocation, property)) { return(true); } } } } return(false); }
private static bool IsReturned(ILocalSymbol symbol, BlockSyntax block, SemanticModel semanticModel, CancellationToken cancellationToken) { using (var walker = ReturnValueWalker.Borrow(block, Search.TopLevel, semanticModel, cancellationToken)) { foreach (var value in walker) { var returnedSymbol = semanticModel.GetSymbolSafe(value, cancellationToken); if (SymbolComparer.Equals(symbol, returnedSymbol)) { return(true); } if (value is ObjectCreationExpressionSyntax objectCreation) { if (objectCreation.ArgumentList != null) { foreach (var argument in objectCreation.ArgumentList.Arguments) { var arg = semanticModel.GetSymbolSafe(argument.Expression, cancellationToken); if (SymbolComparer.Equals(symbol, arg)) { return(true); } } } if (objectCreation.Initializer != null) { foreach (var argument in objectCreation.Initializer.Expressions) { var arg = semanticModel.GetSymbolSafe(argument, cancellationToken); if (SymbolComparer.Equals(symbol, arg)) { return(true); } } } } if (value is InvocationExpressionSyntax invocation) { if (returnedSymbol == KnownSymbol.RxDisposable.Create && invocation.ArgumentList != null && invocation.ArgumentList.Arguments.TryGetSingle(out ArgumentSyntax argument) && argument.Expression is ParenthesizedLambdaExpressionSyntax lambda) { var body = lambda.Body; using (var pooledInvocations = InvocationWalker.Borrow(body)) { foreach (var candidate in pooledInvocations.Invocations) { if (Disposable.IsDisposing(candidate, symbol, semanticModel, cancellationToken)) { return(true); } } } } } } } return(false); }
private static Result IsArgumentAssignedToDisposable(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 == KnownSymbol.CompositeDisposable.Add) { return(Result.Yes); } if (TryGetAssignedFieldOrProperty(argument, method, semanticModel, cancellationToken, out _)) { return(Result.Yes); } if (method.TrySingleDeclaration(cancellationToken, out BaseMethodDeclarationSyntax declaration) && method.TryFindParameter(argument, out var parameter)) { using (visited = visited.IncrementUsage()) { using (var walker = InvocationWalker.Borrow(declaration)) { foreach (var nested in walker) { if (nested.ArgumentList != null && nested.ArgumentList.Arguments.TryFirst(x => x.Expression is IdentifierNameSyntax identifierName && identifierName.Identifier.ValueText == parameter.Name, out var nestedArg)) { switch (IsArgumentAssignedToDisposable(nestedArg, semanticModel, cancellationToken, visited)) { case Result.Unknown: break; case Result.Yes: return(Result.Yes); case Result.AssumeYes: return(Result.AssumeYes); case Result.No: break; case Result.AssumeNo: break; default: throw new ArgumentOutOfRangeException(); } } } } } } return(Result.No); } } return(Result.No); }
internal static bool IsDisposedBefore(ISymbol symbol, ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken) { if (TryGetScope(expression, out var block)) { using (var walker = InvocationWalker.Borrow(block)) { foreach (var invocation in walker.Invocations) { if (invocation.IsExecutedBefore(expression) == ExecutedBefore.No) { continue; } if (DisposeCall.IsDisposing(invocation, symbol, semanticModel, cancellationToken) && !IsReassignedAfter(block, invocation)) { return(true); } } } } if (expression is AssignmentExpressionSyntax assignmentExpression && semanticModel.GetSymbolSafe(assignmentExpression.Left, cancellationToken) is IPropertySymbol property && property.TryGetSetter(cancellationToken, out var setter)) { using (var pooled = InvocationWalker.Borrow(setter)) { foreach (var invocation in pooled.Invocations) { if ((DisposeCall.IsDisposing(invocation, symbol, semanticModel, cancellationToken) || DisposeCall.IsDisposing(invocation, property, semanticModel, cancellationToken)) && !IsReassignedAfter(setter, invocation)) { return(true); } } } } return(false); bool TryGetScope(SyntaxNode node, out BlockSyntax result) { result = null; if (node.FirstAncestor <AnonymousFunctionExpressionSyntax>() is AnonymousFunctionExpressionSyntax lambda) { result = lambda.Body as BlockSyntax; } else if (node.FirstAncestor <AccessorDeclarationSyntax>() is AccessorDeclarationSyntax accessor) { result = accessor.Body; } else if (node.FirstAncestor <BaseMethodDeclarationSyntax>() is BaseMethodDeclarationSyntax method) { result = method.Body; } return(result != null); } bool IsReassignedAfter(SyntaxNode scope, InvocationExpressionSyntax disposeCall) { using (var walker = MutationWalker.Borrow(scope, Scope.Member, semanticModel, cancellationToken)) { foreach (var mutation in walker.All()) { if (mutation.TryFirstAncestor(out StatementSyntax statement) && disposeCall.IsExecutedBefore(statement) == ExecutedBefore.Yes && statement.IsExecutedBefore(expression) == ExecutedBefore.Yes) { return(true); } } } return(false); } }
internal static bool IsAddedToFieldOrProperty(ISymbol symbol, SyntaxNode scope, SemanticModel semanticModel, CancellationToken cancellationToken, PooledSet <ISymbol> recursion = null) { using (var pooledInvocations = InvocationWalker.Borrow(scope)) { foreach (var invocation in pooledInvocations.Invocations) { if (TryGetArgument(invocation, out var argument) && semanticModel.GetSymbolSafe(invocation, cancellationToken) is IMethodSymbol candidate) { if (IsAddMethod(candidate) && symbol.Equals(semanticModel.GetSymbolSafe(argument.Expression, cancellationToken))) { return(true); } if (candidate.TrySingleDeclaration(cancellationToken, out BaseMethodDeclarationSyntax declaration) && candidate.TryFindParameter(argument, out var parameter)) { using (var visited = recursion.IncrementUsage()) { if (visited.Add(parameter) && IsAddedToFieldOrProperty(parameter, declaration, semanticModel, cancellationToken, visited)) { return(true); } } } } } } return(false); bool TryGetArgument(InvocationExpressionSyntax invocation, out ArgumentSyntax argument) { argument = null; if (invocation.ArgumentList is ArgumentListSyntax argumentList) { foreach (var candidate in argumentList.Arguments) { if (symbol.IsEither <ILocalSymbol, IParameterSymbol>()) { if (candidate.Expression is IdentifierNameSyntax identifierName && identifierName.Identifier.ValueText == symbol.Name) { argument = candidate; return(true); } if (candidate.Expression is DeclarationExpressionSyntax declaration && declaration.Designation is SingleVariableDesignationSyntax singleVariable && singleVariable.Identifier.ValueText == symbol.Name) { argument = candidate; return(true); } } else if (semanticModel.TryGetSymbol(candidate.Expression, cancellationToken, out ISymbol candidateSymbol) && symbol.Equals(candidateSymbol)) { argument = candidate; return(true); } } } return(false); } bool IsAddMethod(IMethodSymbol candidate) { switch (candidate.Name) { case "Add": case "Insert": case "Push": case "Enqueue": case "TryAdd": case "TryUpdate": return(candidate.DeclaringSyntaxReferences.Length == 0 && candidate.ContainingType.IsAssignableTo(KnownSymbol.IEnumerable, semanticModel.Compilation)); } return(false); } }
internal static bool IsReturned(ISymbol symbol, SyntaxNode scope, SemanticModel semanticModel, CancellationToken cancellationToken) { using (var walker = ReturnValueWalker.Borrow(scope, ReturnValueSearch.TopLevel, semanticModel, cancellationToken)) { foreach (var value in walker) { var candidate = value; switch (candidate) { case CastExpressionSyntax castExpression: candidate = castExpression.Expression; break; case BinaryExpressionSyntax binary when binary.IsKind(SyntaxKind.AsExpression): candidate = binary.Left; break; } if (candidate is ObjectCreationExpressionSyntax objectCreation) { if (objectCreation.ArgumentList != null) { foreach (var argument in objectCreation.ArgumentList.Arguments) { if (semanticModel.TryGetSymbol(argument.Expression, cancellationToken, out ISymbol argumentSymbol) && symbol.Equals(argumentSymbol)) { return(true); } } } if (objectCreation.Initializer != null) { foreach (var expression in objectCreation.Initializer.Expressions) { if (semanticModel.TryGetSymbol(expression, cancellationToken, out ISymbol expressionSymbol) && symbol.Equals(expressionSymbol)) { return(true); } } } } if (semanticModel.TryGetSymbol(candidate, cancellationToken, out ISymbol returnedSymbol) && symbol.Equals(returnedSymbol)) { return(true); } if (candidate is InvocationExpressionSyntax invocation) { if (returnedSymbol == KnownSymbol.RxDisposable.Create && invocation.ArgumentList != null && invocation.ArgumentList.Arguments.TrySingle(out var argument) && argument.Expression is ParenthesizedLambdaExpressionSyntax lambda) { var body = lambda.Body; using (var pooledInvocations = InvocationWalker.Borrow(body)) { foreach (var disposeCandidate in pooledInvocations.Invocations) { if (DisposeCall.IsDisposing(disposeCandidate, symbol, semanticModel, cancellationToken)) { return(true); } } } } } } } return(false); }