private void AnalyzeOperationBlock(
            OperationBlockAnalysisContext operationBlockContext,
            DisposeAnalysisHelper disposeAnalysisHelper,
            ConcurrentDictionary <Location, bool> reportedLocations)
        {
            // We are only intersted in analyzing method bodies that have at least one disposable object creation.
            if (!(operationBlockContext.OwningSymbol is IMethodSymbol containingMethod) ||
                !disposeAnalysisHelper.HasAnyDisposableCreationDescendant(operationBlockContext.OperationBlocks, containingMethod))
            {
                return;
            }

            PerformFlowAnalysisOnOperationBlock(operationBlockContext, disposeAnalysisHelper, reportedLocations, containingMethod);
        }
Exemple #2
0
        protected override void InitializeWorker(AnalysisContext context)
        {
            context.RegisterCompilationStartAction(compilationContext =>
            {
                if (!DisposeAnalysisHelper.TryCreate(compilationContext.Compilation, out var disposeAnalysisHelper))
                {
                    return;
                }

                // Register a symbol start action to analyze all named types.
                compilationContext.RegisterSymbolStartAction(
                    symbolStartContext => SymbolAnalyzer.OnSymbolStart(symbolStartContext, disposeAnalysisHelper),
                    SymbolKind.NamedType);
            });
        }
        protected override void InitializeWorker(AnalysisContext context)
        {
            context.RegisterCompilationStartAction(compilationContext =>
            {
                if (!DisposeAnalysisHelper.TryCreate(compilationContext.Compilation, out var disposeAnalysisHelper))
                {
                    return;
                }

                // Avoid reporting duplicate diagnostics from interprocedural analysis.
                var reportedLocations = new ConcurrentDictionary <Location, bool>();

                compilationContext.RegisterOperationBlockAction(
                    operationBlockContext => AnalyzeOperationBlock(operationBlockContext, disposeAnalysisHelper, reportedLocations));
            });
        }
        public static bool TryCreate(Compilation compilation, out DisposeAnalysisHelper disposeHelper)
        {
            var disposableType = compilation.SystemIDisposableType();

            if (disposableType == null)
            {
                disposeHelper = null;
                return(false);
            }

            var taskType = compilation.TaskType();
            var disposeOwnershipTransferLikelyTypes = GetDisposeOwnershipTransferLikelyTypes(compilation);

            disposeHelper = new DisposeAnalysisHelper(disposableType, taskType, disposeOwnershipTransferLikelyTypes);
            return(true);
        }
        private void PerformFlowAnalysisOnOperationBlock(
            OperationBlockAnalysisContext operationBlockContext,
            DisposeAnalysisHelper disposeAnalysisHelper,
            ConcurrentDictionary <Location, bool> reportedLocations,
            IMethodSymbol containingMethod)
        {
            // We can skip interprocedural analysis for certain invocations.
            var interproceduralAnalysisPredicateOpt = new InterproceduralAnalysisPredicate(
                skipAnalysisForInvokedMethodPredicateOpt: SkipInterproceduralAnalysis,
                skipAnalysisForInvokedLambdaOrLocalFunctionPredicateOpt: null,
                skipAnalysisForInvokedContextPredicateOpt: null);

            // Compute dispose dataflow analysis result for the operation block.
            if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockContext, containingMethod,
                                                            _disposeObjectsBeforeLosingScopeRule,
                                                            InterproceduralAnalysisKind.ContextSensitive,
                                                            trackInstanceFields: false,
                                                            out var disposeAnalysisResult, out var pointsToAnalysisResult,
                                                            interproceduralAnalysisPredicateOpt))
            {
                var notDisposedDiagnostics = ArrayBuilder <Diagnostic> .GetInstance();

                var mayBeNotDisposedDiagnostics = ArrayBuilder <Diagnostic> .GetInstance();

                try
                {
                    // Compute diagnostics for undisposed objects at exit block.
                    var exitBlock         = disposeAnalysisResult.ControlFlowGraph.ExitBlock();
                    var disposeDataAtExit = disposeAnalysisResult.ExitBlockOutput.Data;
                    ComputeDiagnostics(disposeDataAtExit, notDisposedDiagnostics, mayBeNotDisposedDiagnostics,
                                       disposeAnalysisResult, pointsToAnalysisResult);

                    if (disposeAnalysisResult.ControlFlowGraph.OriginalOperation.HasAnyOperationDescendant(o => o.Kind == OperationKind.None))
                    {
                        // Workaround for https://github.com/dotnet/roslyn/issues/32100
                        // Bail out in presence of OperationKind.None - not implemented IOperation.
                        return;
                    }

                    // Report diagnostics preferring *not* disposed diagnostics over may be not disposed diagnostics
                    // and avoiding duplicates.
                    foreach (var diagnostic in notDisposedDiagnostics.Concat(mayBeNotDisposedDiagnostics))
                    {
                        if (reportedLocations.TryAdd(diagnostic.Location, true))
                        {
                            operationBlockContext.ReportDiagnostic(diagnostic);
                        }
                    }
                }
                finally
                {
                    notDisposedDiagnostics.Free();
                    mayBeNotDisposedDiagnostics.Free();
                }
            }

            return;

            // Local functions.
            bool SkipInterproceduralAnalysis(IMethodSymbol invokedMethod)
            {
                // Skip interprocedural analysis if we are invoking a method and not passing any disposable object as an argument
                // and not receiving a disposable object as a return value.
                // We also check that we are not passing any object type argument which might hold disposable object
                // and also check that we are not passing delegate type argument which can
                // be a lambda or local function that has access to disposable object in current method's scope.

                if (CanBeDisposable(invokedMethod.ReturnType))
                {
                    return(false);
                }

                foreach (var p in invokedMethod.Parameters)
                {
                    if (CanBeDisposable(p.Type))
                    {
                        return(false);
                    }
                }

                return(true);

                bool CanBeDisposable(ITypeSymbol type)
                => type.SpecialType == SpecialType.System_Object ||
                type.IsDisposable(disposeAnalysisHelper.IDisposableType) ||
                type.TypeKind == TypeKind.Delegate;
            }

            void ComputeDiagnostics(
                ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeData,
                ArrayBuilder <Diagnostic> notDisposedDiagnostics,
                ArrayBuilder <Diagnostic> mayBeNotDisposedDiagnostics,
                DisposeAnalysisResult disposeAnalysisResult,
                PointsToAnalysisResult pointsToAnalysisResult)
            {
                foreach (var kvp in disposeData)
                {
                    var location     = kvp.Key;
                    var disposeValue = kvp.Value;

                    // Ignore non-disposable locations and locations without a Creation operation.
                    if (disposeValue.Kind == DisposeAbstractValueKind.NotDisposable ||
                        location.CreationOpt == null)
                    {
                        continue;
                    }

                    // Check if the disposable creation is definitely not disposed or may be not disposed.
                    var isNotDisposed = disposeValue.Kind == DisposeAbstractValueKind.NotDisposed ||
                                        (disposeValue.DisposingOrEscapingOperations.Count > 0 &&
                                         disposeValue.DisposingOrEscapingOperations.All(d => d.IsInsideCatchRegion(disposeAnalysisResult.ControlFlowGraph) && !location.CreationOpt.IsInsideCatchRegion(disposeAnalysisResult.ControlFlowGraph)));
                    var isMayBeNotDisposed = !isNotDisposed &&
                                             (disposeValue.Kind == DisposeAbstractValueKind.MaybeDisposed || disposeValue.Kind == DisposeAbstractValueKind.NotDisposedOrEscaped);

                    if (isNotDisposed || isMayBeNotDisposed)
                    {
                        var syntax = location.TryGetNodeToReportDiagnostic(pointsToAnalysisResult);
                        if (syntax == null)
                        {
                            continue;
                        }

                        var rule = isNotDisposed ? _disposeObjectsBeforeLosingScopeRule : _useRecommendedDisposePatternRule;

                        // Ensure that we do not include multiple lines for the object creation expression in the diagnostic message.
                        var objectCreationText = syntax.ToString();
                        var indexOfNewLine     = objectCreationText.IndexOf(Environment.NewLine);
                        if (indexOfNewLine > 0)
                        {
                            objectCreationText = objectCreationText.Substring(0, indexOfNewLine);
                        }

                        var diagnostic = Diagnostic.Create(
                            rule,
                            syntax.GetLocation(),
                            additionalLocations: null,
                            properties: null,
                            objectCreationText);

                        if (isNotDisposed)
                        {
                            notDisposedDiagnostics.Add(diagnostic);
                        }
                        else
                        {
                            mayBeNotDisposedDiagnostics.Add(diagnostic);
                        }
                    }
                }
            }
        }
Exemple #6
0
            public static void OnSymbolStart(SymbolStartAnalysisContext symbolStartContext, DisposeAnalysisHelper disposeAnalysisHelper)
            {
                // We only want to analyze types which are disposable (implement System.IDisposable directly or indirectly)
                // and have at least one disposable field.
                var namedType = (INamedTypeSymbol)symbolStartContext.Symbol;

                if (!namedType.IsDisposable(disposeAnalysisHelper.IDisposableType))
                {
                    return;
                }

                var disposableFields = disposeAnalysisHelper.GetDisposableFields(namedType);

                if (disposableFields.IsEmpty)
                {
                    return;
                }

                var analyzer = new SymbolAnalyzer(disposableFields, disposeAnalysisHelper);

                // Register an operation block action to analyze disposable assignments and dispose invocations for fields.
                symbolStartContext.RegisterOperationBlockStartAction(analyzer.OnOperationBlockStart);

                // Register symbol end action for containing type to report non-disposed fields.
                // We report fields that have disposable type (implement System.IDisposable directly or indirectly)
                // and were assigned a disposable object within this containing type, but were not disposed in
                // containing type's Dispose method.
                symbolStartContext.RegisterSymbolEndAction(analyzer.OnSymbolEnd);
            }
            public SymbolAnalyzer(DiagnosticDescriptor disposableFieldsShouldBeDisposedRule, ImmutableHashSet <IFieldSymbol> disposableFields, DisposeAnalysisHelper disposeAnalysisHelper)
            {
                Debug.Assert(!disposableFields.IsEmpty);

                _disposableFieldsShouldBeDisposedRule = disposableFieldsShouldBeDisposedRule;
                _disposableFields      = disposableFields;
                _disposeAnalysisHelper = disposeAnalysisHelper;
                _fieldDisposeValueMap  = new ConcurrentDictionary <IFieldSymbol, bool>();
            }