private static void ComputeDiagnostics(
            ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeData,
            ArrayBuilder <Diagnostic> notDisposedDiagnostics,
            ArrayBuilder <Diagnostic> mayBeNotDisposedDiagnostics,
            DisposeAnalysisResult disposeAnalysisResult,
            PointsToAnalysisResult pointsToAnalysisResult,
            DisposeAnalysisKind disposeAnalysisKind,
            bool isDisposeDataForExceptionPaths)
        {
            foreach (var kvp in disposeData)
            {
                AbstractLocation     location     = kvp.Key;
                DisposeAbstractValue disposeValue = kvp.Value;
                if (disposeValue.Kind == DisposeAbstractValueKind.NotDisposable ||
                    location.CreationOpt == null)
                {
                    continue;
                }

                var isNotDisposed = disposeValue.Kind == DisposeAbstractValueKind.NotDisposed ||
                                    (disposeValue.DisposingOrEscapingOperations.Count > 0 &&
                                     disposeValue.DisposingOrEscapingOperations.All(d => d.IsInsideCatchRegion(disposeAnalysisResult.ControlFlowGraph)));
                var isMayBeNotDisposed = !isNotDisposed && (disposeValue.Kind == DisposeAbstractValueKind.MaybeDisposed || disposeValue.Kind == DisposeAbstractValueKind.NotDisposedOrEscaped);

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

                    // CA2000: Call System.IDisposable.Dispose on object created by '{0}' before all references to it are out of scope.
                    var rule       = GetRule(isNotDisposed);
                    var argument   = syntax.ToString();
                    var diagnostic = syntax.CreateDiagnostic(rule, argument);
                    if (isNotDisposed)
                    {
                        notDisposedDiagnostics.Add(diagnostic);
                    }
                    else
                    {
                        mayBeNotDisposedDiagnostics.Add(diagnostic);
                    }
                }
            }

            DiagnosticDescriptor GetRule(bool isNotDisposed)
            {
                if (isNotDisposed)
                {
                    return(isDisposeDataForExceptionPaths ? NotDisposedOnExceptionPathsRule : NotDisposedRule);
                }
                else
                {
                    return(isDisposeDataForExceptionPaths ? MayBeDisposedOnExceptionPathsRule : MayBeDisposedRule);
                }
            }
        }
Beispiel #2
0
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(compilationContext =>
            {
                if (!DisposeAnalysisHelper.TryGetOrCreate(compilationContext.Compilation, out DisposeAnalysisHelper disposeAnalysisHelper))
                {
                    return;
                }

                compilationContext.RegisterOperationBlockAction(operationBlockContext =>
                {
                    if (!(operationBlockContext.OwningSymbol is IMethodSymbol containingMethod) ||
                        !disposeAnalysisHelper.HasAnyDisposableCreationDescendant(operationBlockContext.OperationBlocks, containingMethod))
                    {
                        return;
                    }

                    DataFlowAnalysisResult <DisposeBlockAnalysisResult, DisposeAbstractValue> disposeAnalysisResult;
                    if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockContext.OperationBlocks, containingMethod, out disposeAnalysisResult))
                    {
                        BasicBlock exitBlock = disposeAnalysisResult.ControlFlowGraph.Exit;
                        ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeDataAtExit = disposeAnalysisResult[exitBlock].OutputData;
                        foreach (var kvp in disposeDataAtExit)
                        {
                            AbstractLocation location         = kvp.Key;
                            DisposeAbstractValue disposeValue = kvp.Value;
                            if (disposeValue.Kind == DisposeAbstractValueKind.NotDisposable ||
                                location.CreationOpt == null)
                            {
                                continue;
                            }

                            if (disposeValue.Kind == DisposeAbstractValueKind.NotDisposed ||
                                (disposeValue.DisposingOrEscapingOperations.Count > 0 &&
                                 disposeValue.DisposingOrEscapingOperations.All(d => d.IsInsideCatchClause())))
                            {
                                // CA2000: In method '{0}', call System.IDisposable.Dispose on object created by '{1}' before all references to it are out of scope.
                                var arg1       = containingMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
                                var arg2       = location.CreationOpt.Syntax.ToString();
                                var diagnostic = location.CreationOpt.Syntax.CreateDiagnostic(Rule, arg1, arg2);
                                operationBlockContext.ReportDiagnostic(diagnostic);
                            }
                        }
                    }
                });
            });
        }
Beispiel #3
0
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(compilationContext =>
            {
                var iDisposable = WellKnownTypes.IDisposable(compilationContext.Compilation);
                if (iDisposable == null)
                {
                    return;
                }

                var iCollection        = WellKnownTypes.ICollection(compilationContext.Compilation);
                var genericICollection = WellKnownTypes.GenericICollection(compilationContext.Compilation);
                var disposeOwnershipTransferLikelyTypes = GetDisposeOwnershipTransferLikelyTypes(compilationContext.Compilation);
                compilationContext.RegisterOperationBlockStartAction(operationBlockStartContext =>
                {
                    bool hasDisposableCreation = false;
                    operationBlockStartContext.RegisterOperationAction(operationContext =>
                    {
                        if (!hasDisposableCreation &&
                            operationContext.Operation.Type.IsDisposable(iDisposable))
                        {
                            hasDisposableCreation = true;
                        }
                    },
                                                                       OperationKind.ObjectCreation,
                                                                       OperationKind.TypeParameterObjectCreation,
                                                                       OperationKind.DynamicObjectCreation,
                                                                       OperationKind.Invocation);

                    operationBlockStartContext.RegisterOperationBlockEndAction(operationBlockEndContext =>
                    {
                        if (!hasDisposableCreation ||
                            !(operationBlockEndContext.OwningSymbol is IMethodSymbol containingMethod))
                        {
                            return;
                        }

                        foreach (var operationRoot in operationBlockEndContext.OperationBlocks)
                        {
                            IBlockOperation topmostBlock = operationRoot.GetTopmostParentBlock();
                            if (topmostBlock != null)
                            {
                                var cfg = ControlFlowGraph.Create(topmostBlock);
                                var nullAnalysisResult     = NullAnalysis.GetOrComputeResult(cfg, containingMethod.ContainingType);
                                var pointsToAnalysisResult = PointsToAnalysis.GetOrComputeResult(cfg, containingMethod.ContainingType, nullAnalysisResult);
                                var disposeAnalysisResult  = DisposeAnalysis.GetOrComputeResult(cfg, iDisposable, iCollection,
                                                                                                genericICollection, disposeOwnershipTransferLikelyTypes, containingMethod.ContainingType, pointsToAnalysisResult, nullAnalysisResult);
                                ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeDataAtExit = disposeAnalysisResult[cfg.Exit].InputData;
                                foreach (var kvp in disposeDataAtExit)
                                {
                                    AbstractLocation location         = kvp.Key;
                                    DisposeAbstractValue disposeValue = kvp.Value;
                                    if (disposeValue.Kind == DisposeAbstractValueKind.NotDisposed ||
                                        ((disposeValue.Kind == DisposeAbstractValueKind.Disposed ||
                                          disposeValue.Kind == DisposeAbstractValueKind.MaybeDisposed) &&
                                         disposeValue.DisposingOperations.Count > 0 &&
                                         disposeValue.DisposingOperations.All(d => d.IsInsideCatchClause())))
                                    {
                                        Debug.Assert(location.CreationOpt != null);

                                        // CA2000: In method '{0}', call System.IDisposable.Dispose on object created by '{1}' before all references to it are out of scope.
                                        var arg1       = containingMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
                                        var arg2       = location.CreationOpt.Syntax.ToString();
                                        var diagnostic = location.CreationOpt.Syntax.CreateDiagnostic(Rule, arg1, arg2);
                                        operationBlockEndContext.ReportDiagnostic(diagnostic);
                                    }
                                }

                                break;
                            }
                        }
                    });
                });
            });
        }
Beispiel #4
0
        private static void ComputeDiagnostics(
            ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeData,
            ArrayBuilder <Diagnostic> notDisposedDiagnostics,
            ArrayBuilder <Diagnostic> mayBeNotDisposedDiagnostics,
            DisposeAnalysisResult disposeAnalysisResult,
            PointsToAnalysisResult pointsToAnalysisResult,
            DisposeAnalysisKind disposeAnalysisKind,
            bool isDisposeDataForExceptionPaths)
        {
            foreach (var kvp in disposeData)
            {
                AbstractLocation     location     = kvp.Key;
                DisposeAbstractValue disposeValue = kvp.Value;
                if (disposeValue.Kind == DisposeAbstractValueKind.NotDisposable ||
                    location.Creation == null)
                {
                    continue;
                }

                var isNotDisposed = disposeValue.Kind == DisposeAbstractValueKind.NotDisposed ||
                                    (!disposeValue.DisposingOrEscapingOperations.IsEmpty &&
                                     disposeValue.DisposingOrEscapingOperations.All(d => d.IsInsideCatchRegion(disposeAnalysisResult.ControlFlowGraph) && !location.GetTopOfCreationCallStackOrCreation().IsInsideCatchRegion(disposeAnalysisResult.ControlFlowGraph)));
                var isMayBeNotDisposed = !isNotDisposed && (disposeValue.Kind == DisposeAbstractValueKind.MaybeDisposed || disposeValue.Kind == DisposeAbstractValueKind.NotDisposedOrEscaped);

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

                    // CA2000: Call System.IDisposable.Dispose on object created by '{0}' before all references to it are out of scope.
                    var rule = GetRule(isNotDisposed);

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

                    var diagnostic = syntax.CreateDiagnostic(rule, argument);
                    if (isNotDisposed)
                    {
                        notDisposedDiagnostics.Add(diagnostic);
                    }
                    else
                    {
                        mayBeNotDisposedDiagnostics.Add(diagnostic);
                    }
                }
            }

            DiagnosticDescriptor GetRule(bool isNotDisposed)
            {
                if (isNotDisposed)
                {
                    return(isDisposeDataForExceptionPaths ? NotDisposedOnExceptionPathsRule : NotDisposedRule);
                }
                else
                {
                    return(isDisposeDataForExceptionPaths ? MayBeDisposedOnExceptionPathsRule : MayBeDisposedRule);
                }
            }
        }
        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,
                                                            s_disposeObjectsBeforeLosingScopeRule, 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.GetExit();
                    var disposeDataAtExit = disposeAnalysisResult.ExitBlockOutput.Data;
                    ComputeDiagnostics(disposeDataAtExit, notDisposedDiagnostics, mayBeNotDisposedDiagnostics,
                                       disposeAnalysisResult, pointsToAnalysisResult);

                    // 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)
                {
                    AbstractLocation     location     = kvp.Key;
                    DisposeAbstractValue 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 ? s_disposeObjectsBeforeLosingScopeRule : s_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);
                        }
                    }
                }
            }
        }