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); }
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); } } } } }
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>(); }