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 Result IsAssignedWithCreated(ISymbol symbol, ExpressionSyntax location, SemanticModel semanticModel, CancellationToken cancellationToken) { using (var assignedValues = AssignedValueWalker.Borrow(symbol, location, semanticModel, cancellationToken)) { using (var recursive = RecursiveValues.Borrow(assignedValues, semanticModel, cancellationToken)) { return(IsAnyCreation(recursive, semanticModel, cancellationToken)); } } }
private static bool IsAssignedWithInjected(ISymbol symbol, ExpressionSyntax location, SemanticModel semanticModel, CancellationToken cancellationToken) { using (var assignedValues = AssignedValueWalker.Borrow(symbol, location, semanticModel, cancellationToken)) { using (var recursive = RecursiveValues.Borrow(assignedValues, semanticModel, cancellationToken)) { return(IsAnyCachedOrInjected(recursive, semanticModel, cancellationToken).IsEither(Result.Yes, Result.AssumeYes)); } } }
internal static bool IsCachedOrInjectedOnly(ExpressionSyntax value, ExpressionSyntax location, SemanticModel semanticModel, CancellationToken cancellationToken) { if (semanticModel.TryGetSymbol(value, cancellationToken, out var symbol)) { using var assignedValues = AssignedValueWalker.Borrow(symbol, location, semanticModel, cancellationToken); using var recursive = RecursiveValues.Borrow(assignedValues, semanticModel, cancellationToken); return((IsAnyCachedOrInjected(recursive, semanticModel, cancellationToken).IsEither(Result.Yes, Result.AssumeYes) || IsInjectedCore(symbol).IsEither(Result.Yes, Result.AssumeYes)) && !IsAnyCreation(recursive, semanticModel, cancellationToken).IsEither(Result.Yes, Result.AssumeYes)); } return(false); }
/// <summary> /// Check if any path returns a created IDisposable. /// </summary> internal static Result IsCreation(ExpressionSyntax candidate, SemanticModel semanticModel, CancellationToken cancellationToken) { if (candidate == null) { return(Result.Unknown); } switch (candidate.Kind()) { case SyntaxKind.NullLiteralExpression: case SyntaxKind.StringLiteralExpression: case SyntaxKind.NumericLiteralExpression: case SyntaxKind.TrueLiteralExpression: case SyntaxKind.FalseLiteralExpression: case SyntaxKind.BaseExpression: case SyntaxKind.ThisExpression: return(Result.No); } if (!IsPotentiallyAssignableFrom(candidate, semanticModel, cancellationToken)) { 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 recursive = RecursiveValues.Borrow(new[] { candidate }, semanticModel, cancellationToken)) { return(IsAnyCreation(recursive, semanticModel, cancellationToken)); } }
internal static Result IsAnyCachedOrInjected(RecursiveValues values, SemanticModel semanticModel, CancellationToken cancellationToken) { if (values.IsEmpty) { return(Result.No); } var result = Result.No; values.Reset(); while (values.MoveNext()) { if (values.Current is ElementAccessExpressionSyntax elementAccess && semanticModel.TryGetSymbol(elementAccess.Expression, cancellationToken, out var symbol)) { var isInjected = IsInjectedCore(symbol); if (isInjected == Result.Yes) { return(Result.Yes); } if (isInjected == Result.AssumeYes) { result = Result.AssumeYes; } using var assignedValues = AssignedValueWalker.Borrow(values.Current, semanticModel, cancellationToken); using var recursive = RecursiveValues.Borrow(assignedValues, semanticModel, cancellationToken); isInjected = IsAnyCachedOrInjected(recursive, semanticModel, cancellationToken); if (isInjected == Result.Yes) { return(Result.Yes); } if (isInjected == Result.AssumeYes) { result = Result.AssumeYes; } }
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)); }
internal static Result IsAlreadyAssignedWithCreated(ExpressionSyntax disposable, SemanticModel semanticModel, CancellationToken cancellationToken, out ISymbol assignedSymbol) { if (!IsPotentiallyAssignableFrom(disposable, semanticModel, cancellationToken)) { assignedSymbol = null; return(Result.No); } var symbol = semanticModel.GetSymbolSafe(disposable, cancellationToken); if (symbol is IPropertySymbol property && IsAssignableFrom(property.Type, semanticModel.Compilation) && property.TryGetSetter(cancellationToken, out var setter) && (setter.ExpressionBody != null || setter.Body != null)) { using (var assignedSymbols = PooledSet <ISymbol> .Borrow()) { using (var pooledAssigned = AssignmentExecutionWalker.Borrow(setter, Scope.Recursive, semanticModel, cancellationToken)) { foreach (var assigned in pooledAssigned.Assignments) { if (assigned.Right is IdentifierNameSyntax identifierName && identifierName.Identifier.ValueText == "value" && IsPotentiallyAssignableFrom(assigned.Left, semanticModel, cancellationToken) && semanticModel.GetSymbolSafe(assigned.Left, cancellationToken) is ISymbol candidate && candidate.IsEitherKind(SymbolKind.Field, SymbolKind.Property)) { assignedSymbols.Add(candidate); } } } assignedSymbol = null; var result = Result.No; foreach (var candidate in assignedSymbols) { switch (IsAssignedWithCreated(candidate, disposable, semanticModel, cancellationToken)) { case Result.Unknown: if (result == Result.No) { assignedSymbol = candidate; result = Result.Unknown; } break; case Result.Yes: assignedSymbol = candidate; return(Result.Yes); case Result.AssumeYes: assignedSymbol = candidate; result = Result.AssumeYes; break; case Result.No: case Result.AssumeNo: break; default: throw new ArgumentOutOfRangeException(); } } return(result); } } if (symbol is IParameterSymbol parameter && disposable.TryFirstAncestor <ArrowExpressionClauseSyntax>(out _)) { assignedSymbol = null; return(Result.No); } using (var assignedValues = AssignedValueWalker.Borrow(disposable, semanticModel, cancellationToken)) { assignedSymbol = assignedValues.CurrentSymbol; if (assignedValues.Count == 1 && disposable.Parent is AssignmentExpressionSyntax assignment) { if (assignment.Parent is ParenthesizedExpressionSyntax parenthesizedExpression && parenthesizedExpression.Parent is BinaryExpressionSyntax binary && binary.IsKind(SyntaxKind.CoalesceExpression)) { // lazy return(Result.No); } } if (symbol.IsEither <IParameterSymbol, ILocalSymbol>()) { assignedValues.RemoveAll(x => IsReturnedBefore(x)); } using (var recursive = RecursiveValues.Borrow(assignedValues, semanticModel, cancellationToken)) { return(IsAnyCreation(recursive, semanticModel, cancellationToken)); } } bool IsReturnedBefore(ExpressionSyntax expression) { if (expression.TryFirstAncestor(out BlockSyntax block) && block.Statements.TryFirstOfType(out ReturnStatementSyntax _)) { if (expression.TryFirstAncestor <ForEachStatementSyntax>(out _) || expression.TryFirstAncestor <ForStatementSyntax>(out _) || expression.TryFirstAncestor <WhileStatementSyntax>(out _)) { return(true); } return(!block.Contains(disposable) && block.SharesAncestor(disposable, out MemberDeclarationSyntax _)); } return(false); } }