private static bool IsLazyEnumerable(InvocationExpressionSyntax invocation, SemanticModel semanticModel, CancellationToken cancellationToken, PooledSet <SyntaxNode> visited = null) { if (semanticModel.GetSymbolSafe(invocation, cancellationToken) is IMethodSymbol method && method.ReturnType.IsAssignableTo(KnownSymbol.IEnumerable, semanticModel.Compilation) && method.TrySingleDeclaration(cancellationToken, out MethodDeclarationSyntax methodDeclaration)) { if (YieldStatementWalker.Any(methodDeclaration)) { return(true); } using (var walker = ReturnValueWalker.Borrow(methodDeclaration, ReturnValueSearch.TopLevel, semanticModel, cancellationToken)) { using (visited = visited.IncrementUsage()) { foreach (var returnValue in walker) { if (returnValue is InvocationExpressionSyntax nestedInvocation && visited.Add(returnValue) && IsLazyEnumerable(nestedInvocation, semanticModel, cancellationToken, visited)) { return(true); } } } } } return(false); }
internal static bool TryGetDisposedRootMember(InvocationExpressionSyntax disposeCall, SemanticModel semanticModel, CancellationToken cancellationToken, out ExpressionSyntax disposedMember) { if (MemberPath.TryFindRootMember(disposeCall, out disposedMember)) { var property = semanticModel.GetSymbolSafe(disposedMember, cancellationToken) as IPropertySymbol; if (property == null || property.IsAutoProperty(cancellationToken)) { return(true); } if (property.GetMethod == null) { return(false); } foreach (var reference in property.GetMethod.DeclaringSyntaxReferences) { var node = reference.GetSyntax(cancellationToken); using (var pooled = ReturnValueWalker.Borrow(node, Search.TopLevel, semanticModel, cancellationToken)) { if (pooled.Count == 0) { return(false); } return(MemberPath.TryFindRootMember(pooled[0], out disposedMember)); } } } return(false); }
private static void Handle(SyntaxNodeAnalysisContext context) { if (context.ContainingSymbol is IMethodSymbol method && context.Node is MethodDeclarationSyntax methodDeclaration && Disposable.IsAssignableFrom(method.ReturnType, context.Compilation)) { using var walker = ReturnValueWalker.Borrow(methodDeclaration, ReturnValueSearch.RecursiveInside, context.SemanticModel, context.CancellationToken); if (walker.TryFirst(x => IsCreated(x), out _) && walker.TryFirst(x => IsCachedOrInjected(x) && !IsNop(x), out _)) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP015DoNotReturnCachedAndCreated, methodDeclaration.Identifier.GetLocation())); } } bool IsCreated(ExpressionSyntax expression) { return(Disposable.IsCreation(expression, context.SemanticModel, context.CancellationToken) == Result.Yes); } bool IsCachedOrInjected(ExpressionSyntax expression) { return(Disposable.IsCachedOrInjected(expression, expression, context.SemanticModel, context.CancellationToken)); } bool IsNop(ExpressionSyntax expression) { return(Disposable.IsNop(expression, context.SemanticModel, context.CancellationToken)); } }
private static bool IsPotentiallyCachedOrInjectedCore(ExpressionSyntax value, SemanticModel semanticModel, CancellationToken cancellationToken) { var symbol = semanticModel.GetSymbolSafe(value, cancellationToken); if (IsInjectedCore(symbol) == Result.Yes) { return(true); } if (symbol is IPropertySymbol property && !property.IsAutoProperty(cancellationToken)) { using (var returnValues = ReturnValueWalker.Borrow(value, Search.TopLevel, semanticModel, cancellationToken)) { using (var recursive = RecursiveValues.Create(returnValues, semanticModel, cancellationToken)) { return(IsInjectedCore(recursive, semanticModel, cancellationToken) .IsEither(Result.Yes, Result.Maybe)); } } } using (var assignedValues = AssignedValueWalker.Borrow(value, semanticModel, cancellationToken)) { using (var recursive = RecursiveValues.Create(assignedValues, semanticModel, cancellationToken)) { return(IsInjectedCore(recursive, semanticModel, cancellationToken).IsEither(Result.Yes, Result.Maybe)); } } }
/// <summary> /// Check if any path returns a created IDisposable /// </summary> internal static Result IsCreation(ExpressionSyntax candidate, SemanticModel semanticModel, CancellationToken cancellationToken) { if (!IsPotentiallyAssignableTo(candidate, semanticModel, cancellationToken) || candidate is ThisExpressionSyntax || candidate is BaseExpressionSyntax) { return(Result.No); } if (candidate is IdentifierNameSyntax identifierName && identifierName.Identifier.ValueText == "value" && candidate.FirstAncestor <AccessorDeclarationSyntax>() is AccessorDeclarationSyntax accessor && accessor.IsKind(SyntaxKind.SetAccessorDeclaration)) { return(Result.No); } if (candidate is ObjectCreationExpressionSyntax) { return(Result.Yes); } using (var walker = ReturnValueWalker.Borrow(candidate, Search.Recursive, semanticModel, cancellationToken)) { if (walker.Count == 0) { var symbol = semanticModel.GetSymbolSafe(candidate, cancellationToken); if (symbol != null && symbol.DeclaringSyntaxReferences.Length == 0) { return(IsCreationCore(symbol)); } using (var recursive = RecursiveValues.Create(new[] { candidate }, semanticModel, cancellationToken)) { return(IsCreationCore(recursive, semanticModel, cancellationToken)); } } using (var recursive = RecursiveValues.Create(walker, semanticModel, cancellationToken)) { return(IsCreationCore(recursive, semanticModel, cancellationToken)); } } }
internal static bool IsNop(ExpressionSyntax candidate, SemanticModel semanticModel, CancellationToken cancellationToken) { if (semanticModel.TryGetSymbol(candidate, cancellationToken, out ISymbol symbol) && FieldOrProperty.TryCreate(symbol, out var fieldOrProperty) && fieldOrProperty.IsStatic && IsAssignableFrom(fieldOrProperty.Type, semanticModel.Compilation)) { if (fieldOrProperty.Type == KnownSymbol.Task || symbol == KnownSymbol.RxDisposable.Empty) { return(true); } using (var walker = ReturnValueWalker.Borrow(candidate, ReturnValueSearch.Recursive, semanticModel, cancellationToken)) { if (walker.Count > 0) { return(walker.TrySingle(out var value) && semanticModel.TryGetType(value, cancellationToken, out var type) && IsNop(type)); } } using (var walker = AssignedValueWalker.Borrow(symbol, semanticModel, cancellationToken)) { return(walker.TrySingle(out var value) && semanticModel.TryGetType(value, cancellationToken, out var type) && IsNop(type)); } } return(false); bool IsNop(ITypeSymbol type) { return(type.IsSealed && type.BaseType == KnownSymbol.Object && type.TryFindSingleMethod("Dispose", out var disposeMethod) && disposeMethod.Parameters.Length == 0 && disposeMethod.TrySingleDeclaration(cancellationToken, out MethodDeclarationSyntax declaration) && declaration.Body is BlockSyntax body && body.Statements.Count == 0); } }
internal static bool IsCachedOrInjected(ExpressionSyntax value, ExpressionSyntax location, SemanticModel semanticModel, CancellationToken cancellationToken) { var symbol = semanticModel.GetSymbolSafe(value, cancellationToken); if (IsInjectedCore(symbol).IsEither(Result.Yes, Result.AssumeYes)) { return(true); } if (symbol is IPropertySymbol property && !property.IsAutoProperty()) { using (var returnValues = ReturnValueWalker.Borrow(value, ReturnValueSearch.TopLevel, semanticModel, cancellationToken)) { using (var recursive = RecursiveValues.Borrow(returnValues, semanticModel, cancellationToken)) { return(IsAnyCachedOrInjected(recursive, semanticModel, cancellationToken).IsEither(Result.Yes, Result.AssumeYes)); } } } return(IsAssignedWithInjected(symbol, location, semanticModel, cancellationToken)); }
/// <summary> /// Check if any path returns a created IDisposable /// </summary> internal static Result IsCreation(ExpressionSyntax candidate, SemanticModel semanticModel, CancellationToken cancellationToken) { if (!IsPotentiallyAssignableTo(candidate, semanticModel, cancellationToken) || candidate is ThisExpressionSyntax || candidate is BaseExpressionSyntax) { return(Result.No); } if (candidate is ObjectCreationExpressionSyntax) { return(Result.Yes); } using (var walker = ReturnValueWalker.Borrow(candidate, Search.Recursive, semanticModel, cancellationToken)) { if (walker.Count == 0) { var symbol = semanticModel.GetSymbolSafe(candidate, cancellationToken); if (symbol != null && symbol.DeclaringSyntaxReferences.Length == 0) { return(IsCreationCore(candidate, semanticModel, cancellationToken)); } using (var recursive = RecursiveValues.Create(new[] { candidate }, semanticModel, cancellationToken)) { return(IsCreationCore(recursive, semanticModel, cancellationToken)); } } using (var recursive = RecursiveValues.Create(walker, semanticModel, cancellationToken)) { return(IsCreationCore(recursive, semanticModel, cancellationToken)); } } }
internal static bool IsDisposing(InvocationExpressionSyntax disposeCall, ISymbol symbol, SemanticModel semanticModel, CancellationToken cancellationToken) { if (TryGetDisposed(disposeCall, semanticModel, cancellationToken, out var disposed)) { if (disposed.Equals(symbol)) { return(true); } if (disposed is IPropertySymbol property && property.TrySingleDeclaration(cancellationToken, out var declaration)) { using (var walker = ReturnValueWalker.Borrow(declaration, ReturnValueSearch.TopLevel, semanticModel, cancellationToken)) { return(walker.TrySingle(out var returnValue) && MemberPath.TrySingle(returnValue, out var expression) && semanticModel.TryGetSymbol(expression, cancellationToken, out ISymbol nested) && nested.Equals(symbol)); } } } 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 CheckReturnValues(IParameterSymbol parameter, SyntaxNode memberAccess, SemanticModel semanticModel, CancellationToken cancellationToken, PooledHashSet <SyntaxNode> visited) { Result CheckReturnValue(ExpressionSyntax returnValue) { if (returnValue is ObjectCreationExpressionSyntax nestedObjectCreation) { if (nestedObjectCreation.TryGetMatchingArgument(parameter, out var nestedArgument)) { return(IsArgumentDisposedByReturnValue(nestedArgument, semanticModel, cancellationToken, visited)); } return(Result.No); } if (returnValue is InvocationExpressionSyntax nestedInvocation) { if (nestedInvocation.TryGetMatchingArgument(parameter, out var nestedArgument)) { return(IsArgumentDisposedByReturnValue(nestedArgument, semanticModel, cancellationToken, visited)); } return(Result.No); } if (returnValue is MemberAccessExpressionSyntax nestedMemberAccess) { return(IsArgumentDisposedByInvocationReturnValue(nestedMemberAccess, semanticModel, cancellationToken, visited)); } return(Result.Unknown); } var result = Result.No; using (var returnWalker = ReturnValueWalker.Borrow(memberAccess, Search.Recursive, semanticModel, cancellationToken)) { using (visited = PooledHashSet <SyntaxNode> .BorrowOrIncrementUsage(visited)) { if (!visited.Add(memberAccess)) { return(Result.Unknown); } foreach (var returnValue in returnWalker) { switch (CheckReturnValue(returnValue)) { case Result.Unknown: return(Result.Unknown); case Result.Yes: if (result == Result.No) { result = Result.Yes; } break; case Result.AssumeYes: result = Result.AssumeYes; break; case Result.No: return(Result.No); case Result.AssumeNo: return(Result.AssumeNo); default: throw new ArgumentOutOfRangeException(); } } } } return(result); }
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); }
private bool AddRecursiveValues(ExpressionSyntax assignedValue) { if (assignedValue == null || assignedValue.IsMissing || !this.checkedLocations.Add(assignedValue)) { return(false); } if (assignedValue is LiteralExpressionSyntax || assignedValue is DefaultExpressionSyntax || assignedValue is TypeOfExpressionSyntax || assignedValue is ObjectCreationExpressionSyntax || assignedValue is ArrayCreationExpressionSyntax || assignedValue is ImplicitArrayCreationExpressionSyntax || assignedValue is InitializerExpressionSyntax) { this.values.Add(assignedValue); return(true); } var argument = assignedValue.Parent as ArgumentSyntax; if (argument?.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword) == true) { var invocation = assignedValue.FirstAncestor <InvocationExpressionSyntax>(); var invokedMethod = this.semanticModel.GetSymbolSafe(invocation, this.cancellationToken); if (invokedMethod == null || invokedMethod.DeclaringSyntaxReferences.Length == 0) { this.values.Add(invocation); return(true); } var before = this.values.Count; foreach (var reference in invokedMethod.DeclaringSyntaxReferences) { var methodDeclaration = reference.GetSyntax(this.cancellationToken) as MethodDeclarationSyntax; if (methodDeclaration.TryGetMatchingParameter(argument, out ParameterSyntax parameter)) { using (var assignedValues = AssignedValueWalker.Borrow(this.semanticModel.GetDeclaredSymbolSafe(parameter, this.cancellationToken), this.semanticModel, this.cancellationToken)) { assignedValues.HandleInvoke(invokedMethod, invocation.ArgumentList); return(this.AddManyRecursively(assignedValues)); } } } return(before != this.values.Count); } if (assignedValue is BinaryExpressionSyntax binaryExpression) { switch (binaryExpression.Kind()) { case SyntaxKind.CoalesceExpression: var left = this.AddRecursiveValues(binaryExpression.Left); var right = this.AddRecursiveValues(binaryExpression.Right); return(left || right); case SyntaxKind.AsExpression: return(this.AddRecursiveValues(binaryExpression.Left)); default: return(false); } } if (assignedValue is CastExpressionSyntax cast) { return(this.AddRecursiveValues(cast.Expression)); } if (assignedValue is ConditionalExpressionSyntax conditional) { var whenTrue = this.AddRecursiveValues(conditional.WhenTrue); var whenFalse = this.AddRecursiveValues(conditional.WhenFalse); return(whenTrue || whenFalse); } if (assignedValue is AwaitExpressionSyntax @await) { using (var walker = ReturnValueWalker.Borrow(@await, Search.Recursive, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(walker)); } } if (assignedValue is ElementAccessExpressionSyntax) { this.values.Add(assignedValue); return(true); } var symbol = this.semanticModel.GetSymbolSafe(assignedValue, this.cancellationToken); if (symbol == null) { return(false); } if (symbol is IFieldSymbol) { this.values.Add(assignedValue); return(true); } if (symbol is IParameterSymbol) { this.values.Add(assignedValue); using (var assignedValues = AssignedValueWalker.Borrow(assignedValue, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(assignedValues)); } } if (symbol is ILocalSymbol) { using (var assignedValues = AssignedValueWalker.Borrow(assignedValue, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(assignedValues)); } } if (symbol is IPropertySymbol property) { if (property.DeclaringSyntaxReferences.Length == 0) { this.values.Add(assignedValue); return(true); } using (var returnValues = ReturnValueWalker.Borrow(assignedValue, Search.Recursive, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(returnValues)); } } if (symbol is IMethodSymbol method) { if (method.DeclaringSyntaxReferences.Length == 0) { this.values.Add(assignedValue); return(true); } using (var walker = ReturnValueWalker.Borrow(assignedValue, Search.Recursive, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(walker)); } } return(false); }
private static bool MustBeHandled( ExpressionSyntax node, SemanticModel semanticModel, CancellationToken cancellationToken) { if (node.Parent is AnonymousFunctionExpressionSyntax || node.Parent is UsingStatementSyntax || node.Parent is EqualsValueClauseSyntax || node.Parent is ReturnStatementSyntax || node.Parent is ArrowExpressionClauseSyntax) { return(false); } if (Disposable.IsCreation(node, semanticModel, cancellationToken) .IsEither(Result.No, Result.Unknown)) { return(false); } if (node.Parent is StatementSyntax) { return(true); } if (node.Parent is ArgumentSyntax argument) { if (argument.Parent.Parent is InvocationExpressionSyntax invocation) { using (var returnWalker = ReturnValueWalker.Borrow(invocation, Search.Recursive, semanticModel, cancellationToken)) { foreach (var returnValue in returnWalker) { if (MustBeHandled(returnValue, semanticModel, cancellationToken)) { return(true); } } } return(false); } if (argument.Parent.Parent is ObjectCreationExpressionSyntax) { if (TryGetAssignedFieldOrProperty(argument, semanticModel, cancellationToken, out ISymbol member, out IMethodSymbol ctor) && member != null) { var initializer = argument.FirstAncestorOrSelf <ConstructorInitializerSyntax>(); if (initializer != null) { if (semanticModel.GetDeclaredSymbolSafe(initializer.Parent, cancellationToken) is IMethodSymbol chainedCtor && chainedCtor.ContainingType != member.ContainingType) { if (Disposable.TryGetDisposeMethod(chainedCtor.ContainingType, Search.TopLevel, out IMethodSymbol disposeMethod)) { return(!Disposable.IsMemberDisposed(member, disposeMethod, semanticModel, cancellationToken)); } } } if (Disposable.IsMemberDisposed(member, ctor.ContainingType, semanticModel, cancellationToken) .IsEither(Result.Yes, Result.Maybe)) { return(false); } return(true); } return(ctor?.ContainingType != KnownSymbol.StreamReader && ctor?.ContainingType != KnownSymbol.CompositeDisposable); } } return(false); }
private bool AddRecursiveValues(ExpressionSyntax assignedValue) { if (assignedValue == null || assignedValue.IsMissing || !this.checkedLocations.Add(assignedValue)) { return(false); } if (assignedValue.Parent is ArgumentSyntax argument && argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword)) { if (assignedValue.TryFirstAncestor(out InvocationExpressionSyntax invocation) && this.semanticModel.TryGetSymbol(invocation, this.cancellationToken, out var target) && target.TrySingleMethodDeclaration(this.cancellationToken, out var targetDeclaration)) { if (targetDeclaration.TryFindParameter(argument, out var parameter) && this.semanticModel.TryGetSymbol(parameter, this.cancellationToken, out var parameterSymbol)) { using (var assignedValues = AssignedValueWalker.Borrow(parameterSymbol, this.semanticModel, this.cancellationToken)) { assignedValues.HandleInvoke(target, invocation.ArgumentList); return(this.AddManyRecursively(assignedValues)); } } return(false); } this.values.Add(invocation); return(true); } switch (assignedValue) { case ArrayCreationExpressionSyntax _: case DefaultExpressionSyntax _: case ElementAccessExpressionSyntax _: case ImplicitArrayCreationExpressionSyntax _: case InitializerExpressionSyntax _: case LiteralExpressionSyntax _: case ObjectCreationExpressionSyntax _: case TypeOfExpressionSyntax _: this.values.Add(assignedValue); return(true); case BinaryExpressionSyntax binaryExpression: switch (binaryExpression.Kind()) { case SyntaxKind.CoalesceExpression: var left = this.AddRecursiveValues(binaryExpression.Left); var right = this.AddRecursiveValues(binaryExpression.Right); return(left || right); case SyntaxKind.AsExpression: return(this.AddRecursiveValues(binaryExpression.Left)); default: return(false); } case CastExpressionSyntax cast: return(this.AddRecursiveValues(cast.Expression)); case ConditionalExpressionSyntax conditional: var whenTrue = this.AddRecursiveValues(conditional.WhenTrue); var whenFalse = this.AddRecursiveValues(conditional.WhenFalse); return(whenTrue || whenFalse); case AwaitExpressionSyntax awaitExpression: using (var walker = ReturnValueWalker.Borrow(awaitExpression, ReturnValueSearch.RecursiveInside, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(walker)); } } if (this.semanticModel.TryGetSymbol(assignedValue, this.cancellationToken, out ISymbol symbol)) { switch (symbol) { case ILocalSymbol _: using (var assignedValues = AssignedValueWalker.Borrow(assignedValue, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(assignedValues)); } case IParameterSymbol _: this.values.Add(assignedValue); using (var assignedValues = AssignedValueWalker.Borrow(assignedValue, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(assignedValues)); } case IFieldSymbol _: this.values.Add(assignedValue); return(true); case IPropertySymbol _: case IMethodSymbol _: if (symbol.DeclaringSyntaxReferences.Length == 0) { this.values.Add(assignedValue); return(true); } using (var returnValues = ReturnValueWalker.Borrow(assignedValue, ReturnValueSearch.RecursiveInside, this.semanticModel, this.cancellationToken)) { return(this.AddManyRecursively(returnValues)); } } } return(false); }