private static void OnSymbolStart(SymbolStartAnalysisContext symbolStartContext) { if (!DisposeAnalysisHelper.TryGetOrCreate(symbolStartContext.Compilation, out DisposeAnalysisHelper? disposeAnalysisHelper) || !ShouldAnalyze(symbolStartContext, disposeAnalysisHelper)) { return; } var fieldDisposeValueMap = PooledConcurrentDictionary <IFieldSymbol, /*disposed*/ bool> .GetInstance(); var hasErrors = false; symbolStartContext.RegisterOperationAction(_ => hasErrors = true, OperationKind.Invalid); // Disposable fields with initializer at declaration must be disposed. symbolStartContext.RegisterOperationAction(OnFieldInitializer, OperationKind.FieldInitializer); // Instance fields initialized in constructor/method body with a locally created disposable object must be disposed. symbolStartContext.RegisterOperationBlockStartAction(OnOperationBlockStart); // Report diagnostics at symbol end. symbolStartContext.RegisterSymbolEndAction(OnSymbolEnd); return; // Local functions void AddOrUpdateFieldDisposedValue(IFieldSymbol field, bool disposed) { Debug.Assert(!field.IsStatic); Debug.Assert(disposeAnalysisHelper !.IsDisposable(field.Type)); fieldDisposeValueMap.AddOrUpdate(field, addValue: disposed, updateValueFactory: (f, currentValue) => currentValue || disposed); }
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); } } } }); }); }
public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); context.RegisterCompilationStartAction(compilationContext => { if (!DisposeAnalysisHelper.TryGetOrCreate(compilationContext.Compilation, out DisposeAnalysisHelper disposeAnalysisHelper)) { return; } var fieldDisposeValueMap = new ConcurrentDictionary<IFieldSymbol, /*disposed*/bool>(); void addOrUpdateFieldDisposedValue(IFieldSymbol field, bool disposed) { Debug.Assert(!field.IsStatic); Debug.Assert(field.Type.IsDisposable(disposeAnalysisHelper.IDisposable)); fieldDisposeValueMap.AddOrUpdate(field, addValue: disposed, updateValueFactory: (f, currentValue) => currentValue || disposed); }; // Disposable fields with initializer at declaration must be disposed. compilationContext.RegisterOperationAction(operationContext => { var initializedFields = ((IFieldInitializerOperation)operationContext.Operation).InitializedFields; foreach (var field in initializedFields) { if (!field.IsStatic && disposeAnalysisHelper.GetDisposableFields(field.ContainingType).Contains(field)) { addOrUpdateFieldDisposedValue(field, disposed: false); } } }, OperationKind.FieldInitializer); // Instance fields initialized in constructor/method body with a locally created disposable object must be disposed. compilationContext.RegisterOperationBlockStartAction(operationBlockStartContext => { if (!(operationBlockStartContext.OwningSymbol is IMethodSymbol containingMethod)) { return; } if (disposeAnalysisHelper.HasAnyDisposableCreationDescendant(operationBlockStartContext.OperationBlocks, containingMethod)) { if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockStartContext.OperationBlocks, containingMethod, operationBlockStartContext.Options, Rule, trackInstanceFields: false, trackExceptionPaths: false, operationBlockStartContext.CancellationToken, out var disposeAnalysisResult, out var pointsToAnalysisResult)) { Debug.Assert(disposeAnalysisResult != null); Debug.Assert(pointsToAnalysisResult != null); operationBlockStartContext.RegisterOperationAction(operationContext => { var fieldReference = (IFieldReferenceOperation)operationContext.Operation; var field = fieldReference.Field; // Only track instance fields on the current instance. if (field.IsStatic || fieldReference.Instance?.Kind != OperationKind.InstanceReference) { return; } // Check if this is a Disposable field that is not currently being tracked. if (fieldDisposeValueMap.ContainsKey(field) || !disposeAnalysisHelper.GetDisposableFields(field.ContainingType).Contains(field)) { return; } // We have a field reference for a disposable field. // Check if it is being assigned a locally created disposable object. if (fieldReference.Parent is ISimpleAssignmentOperation simpleAssignmentOperation && simpleAssignmentOperation.Target == fieldReference) { PointsToAbstractValue assignedPointsToValue = pointsToAnalysisResult[simpleAssignmentOperation.Value.Kind, simpleAssignmentOperation.Value.Syntax]; foreach (var location in assignedPointsToValue.Locations) { if (disposeAnalysisHelper.IsDisposableCreationOrDisposeOwnershipTransfer(location, containingMethod)) { addOrUpdateFieldDisposedValue(field, disposed: false); break; } } } }, OperationKind.FieldReference); } } // Mark fields disposed in Dispose method(s). if (containingMethod.GetDisposeMethodKind(disposeAnalysisHelper.IDisposable, disposeAnalysisHelper.Task) != DisposeMethodKind.None) { var disposableFields = disposeAnalysisHelper.GetDisposableFields(containingMethod.ContainingType); if (!disposableFields.IsEmpty) { if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockStartContext.OperationBlocks, containingMethod, operationBlockStartContext.Options, Rule, trackInstanceFields: true, trackExceptionPaths: false, cancellationToken: operationBlockStartContext.CancellationToken, disposeAnalysisResult: out var disposeAnalysisResult, pointsToAnalysisResult: out var pointsToAnalysisResult)) { BasicBlock exitBlock = disposeAnalysisResult.ControlFlowGraph.GetExit(); foreach (var fieldWithPointsToValue in disposeAnalysisResult.TrackedInstanceFieldPointsToMap) { IFieldSymbol field = fieldWithPointsToValue.Key; PointsToAbstractValue pointsToValue = fieldWithPointsToValue.Value; Debug.Assert(field.Type.IsDisposable(disposeAnalysisHelper.IDisposable)); ImmutableDictionary<AbstractLocation, DisposeAbstractValue> disposeDataAtExit = disposeAnalysisResult.ExitBlockOutput.Data; var disposed = false; foreach (var location in pointsToValue.Locations) { if (disposeDataAtExit.TryGetValue(location, out DisposeAbstractValue disposeValue)) { switch (disposeValue.Kind) { // For MaybeDisposed, conservatively mark the field as disposed as we don't support path sensitive analysis. case DisposeAbstractValueKind.MaybeDisposed: case DisposeAbstractValueKind.Unknown: case DisposeAbstractValueKind.Escaped: case DisposeAbstractValueKind.Disposed: disposed = true; addOrUpdateFieldDisposedValue(field, disposed); break; } } if (disposed) { break; } } } } } } }); compilationContext.RegisterCompilationEndAction(compilationEndContext => { foreach (var kvp in fieldDisposeValueMap) { IFieldSymbol field = kvp.Key; bool disposed = kvp.Value; if (!disposed) { // '{0}' contains field '{1}' that is of IDisposable type '{2}', but it is never disposed. Change the Dispose method on '{0}' to call Close or Dispose on this field. var arg1 = field.ContainingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); var arg2 = field.Name; var arg3 = field.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); var diagnostic = field.CreateDiagnostic(Rule, arg1, arg2, arg3); compilationEndContext.ReportDiagnostic(diagnostic); } } }); }); }
protected DisposableFieldAnalyzer(Compilation compilation) { DisposeAnalysisHelper.TryGetOrCreate(compilation, out _disposeAnalysisHelper !); RoslynDebug.Assert(_disposeAnalysisHelper != null); }
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.RegisterOperationBlockStartAction(operationBlockStartContext => { if (!(operationBlockStartContext.OwningSymbol is IMethodSymbol containingMethod) || containingMethod.OverriddenMethod == null || containingMethod.OverriddenMethod.IsAbstract) { return; } var disposeMethodKind = disposeAnalysisHelper.GetDisposeMethodKind(containingMethod); switch (disposeMethodKind) { case DisposeMethodKind.Dispose: case DisposeMethodKind.DisposeBool: case DisposeMethodKind.DisposeAsync: case DisposeMethodKind.DisposeCoreAsync: break; case DisposeMethodKind.Close: // FxCop compat: Ignore Close methods due to high false positive rate. return; default: return; } var invokesBaseDispose = false; operationBlockStartContext.RegisterOperationAction(operationContext => { if (invokesBaseDispose) { return; } var invocation = (IInvocationOperation)operationContext.Operation; if (Equals(invocation.TargetMethod, containingMethod.OverriddenMethod) && invocation.Instance is IInstanceReferenceOperation instanceReference && instanceReference.ReferenceKind == InstanceReferenceKind.ContainingTypeInstance) { Debug.Assert(disposeAnalysisHelper.GetDisposeMethodKind(invocation.TargetMethod) == disposeMethodKind); invokesBaseDispose = true; } }, OperationKind.Invocation); operationBlockStartContext.RegisterOperationBlockEndAction(operationEndContext => { if (!invokesBaseDispose) { // Ensure that method '{0}' calls '{1}' in all possible control flow paths. var arg1 = containingMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); var baseKeyword = containingMethod.Language == LanguageNames.CSharp ? "base" : "MyBase"; var disposeMethodParam = (disposeMethodKind == DisposeMethodKind.DisposeBool || disposeMethodKind == DisposeMethodKind.DisposeCoreAsync) ? containingMethod.Language == LanguageNames.CSharp ? "bool" : "Boolean" : string.Empty; var disposeMethodName = disposeMethodKind == DisposeMethodKind.DisposeBool ? "Dispose" : disposeMethodKind.ToString(); var arg2 = $"{baseKeyword}.{disposeMethodName}({disposeMethodParam})"; var diagnostic = containingMethod.CreateDiagnostic(Rule, arg1, arg2); operationEndContext.ReportDiagnostic(diagnostic); } }); }); }); }
public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); context.RegisterCompilationStartAction(compilationContext => { if (!DisposeAnalysisHelper.TryGetOrCreate(compilationContext.Compilation, out DisposeAnalysisHelper? disposeAnalysisHelper)) { return; } var fieldDisposeValueMap = new ConcurrentDictionary <IFieldSymbol, /*disposed*/ bool>(); void addOrUpdateFieldDisposedValue(IFieldSymbol field, bool disposed) { Debug.Assert(!field.IsStatic); Debug.Assert(disposeAnalysisHelper !.IsDisposable(field.Type)); fieldDisposeValueMap.AddOrUpdate(field, addValue: disposed, updateValueFactory: (f, currentValue) => currentValue || disposed); }; var hasErrors = false; compilationContext.RegisterOperationAction(_ => hasErrors = true, OperationKind.Invalid); // Disposable fields with initializer at declaration must be disposed. compilationContext.RegisterOperationAction(operationContext => { if (!ShouldAnalyze(operationContext.ContainingSymbol.ContainingType)) { return; } var initializedFields = ((IFieldInitializerOperation)operationContext.Operation).InitializedFields; foreach (var field in initializedFields) { if (!field.IsStatic && disposeAnalysisHelper.GetDisposableFields(field.ContainingType).Contains(field)) { addOrUpdateFieldDisposedValue(field, disposed: false); } } }, OperationKind.FieldInitializer); // Instance fields initialized in constructor/method body with a locally created disposable object must be disposed. compilationContext.RegisterOperationBlockStartAction(operationBlockStartContext => { if (!(operationBlockStartContext.OwningSymbol is IMethodSymbol containingMethod) || !ShouldAnalyze(containingMethod.ContainingType)) { return; } if (disposeAnalysisHelper.HasAnyDisposableCreationDescendant(operationBlockStartContext.OperationBlocks, containingMethod)) { PointsToAnalysisResult?lazyPointsToAnalysisResult = null; operationBlockStartContext.RegisterOperationAction(operationContext => { var fieldReference = (IFieldReferenceOperation)operationContext.Operation; var field = fieldReference.Field; // Only track instance fields on the current instance. if (field.IsStatic || fieldReference.Instance?.Kind != OperationKind.InstanceReference) { return; } // Check if this is a Disposable field that is not currently being tracked. if (fieldDisposeValueMap.ContainsKey(field) || !disposeAnalysisHelper.GetDisposableFields(field.ContainingType).Contains(field)) { return; } // We have a field reference for a disposable field. // Check if it is being assigned a locally created disposable object. if (fieldReference.Parent is ISimpleAssignmentOperation simpleAssignmentOperation && simpleAssignmentOperation.Target == fieldReference) { if (lazyPointsToAnalysisResult == null) { var cfg = operationBlockStartContext.OperationBlocks.GetControlFlowGraph(); if (cfg == null) { hasErrors = true; return; } var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(operationContext.Compilation); var interproceduralAnalysisConfig = InterproceduralAnalysisConfiguration.Create( operationBlockStartContext.Options, Rule, InterproceduralAnalysisKind.None, operationBlockStartContext.CancellationToken); var pointsToAnalysisResult = PointsToAnalysis.TryGetOrComputeResult(cfg, containingMethod, operationBlockStartContext.Options, wellKnownTypeProvider, interproceduralAnalysisConfig, interproceduralAnalysisPredicateOpt: null, pessimisticAnalysis: false, performCopyAnalysis: false); if (pointsToAnalysisResult == null) { hasErrors = true; return; } Interlocked.CompareExchange(ref lazyPointsToAnalysisResult, pointsToAnalysisResult, null); } PointsToAbstractValue assignedPointsToValue = lazyPointsToAnalysisResult[simpleAssignmentOperation.Value.Kind, simpleAssignmentOperation.Value.Syntax]; foreach (var location in assignedPointsToValue.Locations) { if (disposeAnalysisHelper.IsDisposableCreationOrDisposeOwnershipTransfer(location, containingMethod)) { addOrUpdateFieldDisposedValue(field, disposed: false); break; } } } }, OperationKind.FieldReference); } // Mark fields disposed in Dispose method(s). if (IsDisposeMethod(containingMethod)) { var disposableFields = disposeAnalysisHelper.GetDisposableFields(containingMethod.ContainingType); if (!disposableFields.IsEmpty) { if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockStartContext.OperationBlocks, containingMethod, operationBlockStartContext.Options, Rule, trackInstanceFields: true, trackExceptionPaths: false, cancellationToken: operationBlockStartContext.CancellationToken, disposeAnalysisResult: out var disposeAnalysisResult, pointsToAnalysisResult: out var pointsToAnalysisResult)) { RoslynDebug.Assert(disposeAnalysisResult.TrackedInstanceFieldPointsToMap != null); BasicBlock exitBlock = disposeAnalysisResult.ControlFlowGraph.GetExit(); foreach (var fieldWithPointsToValue in disposeAnalysisResult.TrackedInstanceFieldPointsToMap) { IFieldSymbol field = fieldWithPointsToValue.Key; PointsToAbstractValue pointsToValue = fieldWithPointsToValue.Value; Debug.Assert(disposeAnalysisHelper.IsDisposable(field.Type)); ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeDataAtExit = disposeAnalysisResult.ExitBlockOutput.Data; var disposed = false; foreach (var location in pointsToValue.Locations) { if (disposeDataAtExit.TryGetValue(location, out DisposeAbstractValue disposeValue)) { switch (disposeValue.Kind) { // For MaybeDisposed, conservatively mark the field as disposed as we don't support path sensitive analysis. case DisposeAbstractValueKind.MaybeDisposed: case DisposeAbstractValueKind.Unknown: case DisposeAbstractValueKind.Escaped: case DisposeAbstractValueKind.Disposed: disposed = true; addOrUpdateFieldDisposedValue(field, disposed); break; } } if (disposed) { break; } } } } } } }); compilationContext.RegisterCompilationEndAction(compilationEndContext => { if (hasErrors) { return; } foreach (var kvp in fieldDisposeValueMap) { IFieldSymbol field = kvp.Key; bool disposed = kvp.Value; // Flag non-disposed fields only if the containing type has a Dispose method implementation. if (!disposed && HasDisposeMethod(field.ContainingType)) { // '{0}' contains field '{1}' that is of IDisposable type '{2}', but it is never disposed. Change the Dispose method on '{0}' to call Close or Dispose on this field. var arg1 = field.ContainingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); var arg2 = field.Name; var arg3 = field.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); var diagnostic = field.CreateDiagnostic(Rule, arg1, arg2, arg3); compilationEndContext.ReportDiagnostic(diagnostic); } } }); return; // Local functions bool ShouldAnalyze(INamedTypeSymbol namedType) { // We only want to analyze types which are disposable (implement System.IDisposable directly or indirectly) // and have at least one disposable field. return(!hasErrors && disposeAnalysisHelper !.IsDisposable(namedType) && !disposeAnalysisHelper.GetDisposableFields(namedType).IsEmpty&& !namedType.IsConfiguredToSkipAnalysis(compilationContext.Options, Rule, compilationContext.Compilation, compilationContext.CancellationToken)); } bool IsDisposeMethod(IMethodSymbol method) => disposeAnalysisHelper !.GetDisposeMethodKind(method) != DisposeMethodKind.None; bool HasDisposeMethod(INamedTypeSymbol namedType) { foreach (var method in namedType.GetMembers().OfType <IMethodSymbol>()) { if (IsDisposeMethod(method)) { return(true); } } return(false); } }); }
public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(compilationContext => { if (!DisposeAnalysisHelper.TryGetOrCreate(compilationContext.Compilation, out var disposeAnalysisHelper)) { return; } var reportedLocations = new ConcurrentDictionary <Location, bool>(); compilationContext.RegisterOperationBlockAction(operationBlockContext => { if (!(operationBlockContext.OwningSymbol is IMethodSymbol containingMethod) || !disposeAnalysisHelper.HasAnyDisposableCreationDescendant(operationBlockContext.OperationBlocks, containingMethod) || containingMethod.IsConfiguredToSkipAnalysis(operationBlockContext.Options, NotDisposedRule, operationBlockContext.Compilation, operationBlockContext.CancellationToken)) { return; } var disposeAnalysisKind = operationBlockContext.Options.GetDisposeAnalysisKindOption(NotDisposedOnExceptionPathsRule, containingMethod, operationBlockContext.Compilation, DisposeAnalysisKind.NonExceptionPaths, operationBlockContext.CancellationToken); var trackExceptionPaths = disposeAnalysisKind.AreExceptionPathsEnabled(); // For non-exception paths analysis, we can skip interprocedural analysis for certain invocations. var interproceduralAnalysisPredicateOpt = !trackExceptionPaths ? new InterproceduralAnalysisPredicate( skipAnalysisForInvokedMethodPredicateOpt: SkipInterproceduralAnalysis, skipAnalysisForInvokedLambdaOrLocalFunctionPredicateOpt: null, skipAnalysisForInvokedContextPredicateOpt: null) : null; if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockContext.OperationBlocks, containingMethod, operationBlockContext.Options, NotDisposedRule, trackInstanceFields: false, trackExceptionPaths, operationBlockContext.CancellationToken, 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 for non-exceptional exit paths. var exitBlock = disposeAnalysisResult.ControlFlowGraph.GetExit(); var disposeDataAtExit = disposeAnalysisResult.ExitBlockOutput.Data; ComputeDiagnostics(disposeDataAtExit, notDisposedDiagnostics, mayBeNotDisposedDiagnostics, disposeAnalysisResult, pointsToAnalysisResult, disposeAnalysisKind, isDisposeDataForExceptionPaths: false); if (trackExceptionPaths) { // Compute diagnostics for undisposed objects at handled exception exit paths. var disposeDataAtHandledExceptionPaths = disposeAnalysisResult.ExceptionPathsExitBlockOutputOpt !.Data; ComputeDiagnostics(disposeDataAtHandledExceptionPaths, notDisposedDiagnostics, mayBeNotDisposedDiagnostics, disposeAnalysisResult, pointsToAnalysisResult, disposeAnalysisKind, isDisposeDataForExceptionPaths: true); // Compute diagnostics for undisposed objects at unhandled exception exit paths, if any. var disposeDataAtUnhandledExceptionPaths = disposeAnalysisResult.MergedStateForUnhandledThrowOperationsOpt?.Data; if (disposeDataAtUnhandledExceptionPaths != null) { ComputeDiagnostics(disposeDataAtUnhandledExceptionPaths, notDisposedDiagnostics, mayBeNotDisposedDiagnostics, disposeAnalysisResult, pointsToAnalysisResult, disposeAnalysisKind, isDisposeDataForExceptionPaths: true); } } if (!notDisposedDiagnostics.Any() && !mayBeNotDisposedDiagnostics.Any()) { return; } if (disposeAnalysisResult.ControlFlowGraph.OriginalOperation.HasAnyOperationDescendant(o => o.Kind == OperationKind.None && !(o.Parent is INameOfOperation))) { // 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.DerivesFrom(disposeAnalysisHelper !.IDisposable) || type.TypeKind == TypeKind.Delegate; } }); }