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); }; 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(); 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)) { 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 => { 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 && namedType.IsDisposable(disposeAnalysisHelper.IDisposable) && !disposeAnalysisHelper.GetDisposableFields(namedType).IsEmpty&& !namedType.IsConfiguredToSkipAnalysis(compilationContext.Options, Rule, compilationContext.Compilation, compilationContext.CancellationToken)); } bool IsDisposeMethod(IMethodSymbol method) => method.GetDisposeMethodKind(disposeAnalysisHelper.IDisposable, disposeAnalysisHelper.Task) != 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 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)) { DataFlowAnalysisResult <DisposeBlockAnalysisResult, DisposeAbstractValue> disposeAnalysisResult; DataFlowAnalysisResult <PointsToBlockAnalysisResult, PointsToAbstractValue> pointsToAnalysisResult; if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockStartContext.OperationBlocks, containingMethod, out disposeAnalysisResult, out 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]; 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) { DataFlowAnalysisResult <DisposeBlockAnalysisResult, DisposeAbstractValue> disposeAnalysisResult; DataFlowAnalysisResult <PointsToBlockAnalysisResult, PointsToAbstractValue> pointsToAnalysisResult; ImmutableDictionary <IFieldSymbol, PointsToAbstractValue> trackedInstanceFieldPointsToMap; if (disposeAnalysisHelper.TryGetOrComputeResult(operationBlockStartContext.OperationBlocks, containingMethod, trackInstanceFields: true, disposeAnalysisResult: out disposeAnalysisResult, pointsToAnalysisResult: out pointsToAnalysisResult, trackedInstanceFieldPointsToMap: out trackedInstanceFieldPointsToMap)) { BasicBlock exitBlock = disposeAnalysisResult.ControlFlowGraph.Exit; foreach (var fieldWithPointsToValue in trackedInstanceFieldPointsToMap) { IFieldSymbol field = fieldWithPointsToValue.Key; PointsToAbstractValue pointsToValue = fieldWithPointsToValue.Value; Debug.Assert(field.Type.IsDisposable(disposeAnalysisHelper.IDisposable)); ImmutableDictionary <AbstractLocation, DisposeAbstractValue> disposeDataAtExit = disposeAnalysisResult[exitBlock].OutputData; 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.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); } } }); }); }