public static void InListOfTAdd(string expression) { var code = @" namespace N { using System; using System.Collections.Generic; internal class C { private List<IDisposable> disposables = new List<IDisposable>(); internal C(IDisposable disposable) { this.disposables.Add(disposable); } } }".AssertReplace("Add(disposable)", expression); var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("IDisposable disposable"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual("N.C.disposables", container.ToString()); }
public static void Recursive() { var code = @" namespace N { using System.IO; public class C { public C(Stream stream) { this.disposable = GetReader(stream); } private static StreamReader GetReader(Stream arg) => GetReader(arg); } }"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("Stream stream"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(false, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out _)); }
public static void InLeaveOpen(string expression, bool stores) { var code = @" namespace N { using System; using System.IO; using System.IO.Compression; using System.Security.Cryptography; using System.Text; public class C { private readonly IDisposable disposable; public C(Stream stream) { this.disposable = new BinaryReader(stream); } } }".AssertReplace("new BinaryReader(stream)", expression); var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("stream"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(stores, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual(stores, Disposable.DisposedByReturnValue(syntaxTree.FindArgument("stream"), semanticModel, CancellationToken.None, out _)); if (stores) { Assert.AreEqual("N.C.disposable", container.ToString()); } }
private static void Handle(SyntaxNodeAnalysisContext context) { if (!context.IsExcludedFromAnalysis() && context.Node is ArgumentSyntax argument && argument.Parent is ArgumentListSyntax argumentList && argumentList.Parent is InvocationExpressionSyntax invocation && argument.RefOrOutKeyword.IsEither(SyntaxKind.RefKeyword, SyntaxKind.OutKeyword) && context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out var method) && method.TrySingleDeclaration(context.CancellationToken, out BaseMethodDeclarationSyntax declaration) && method.TryFindParameter(argument, out var parameter) && Disposable.IsPotentiallyAssignableFrom(parameter.Type, context.Compilation)) { if (Disposable.IsCreation(argument, context.SemanticModel, context.CancellationToken).IsEither(Result.Yes, Result.AssumeYes) && !Disposable.IsAddedToFieldOrProperty(parameter, declaration, context.SemanticModel, context.CancellationToken) && context.SemanticModel.TryGetSymbol(argument.Expression, context.CancellationToken, out ISymbol symbol)) { if (LocalOrParameter.TryCreate(symbol, out var localOrParameter) && Disposable.ShouldDispose(localOrParameter, argument.Expression, context.SemanticModel, context.CancellationToken)) { context.ReportDiagnostic(Diagnostic.Create(IDISP001DisposeCreated.Descriptor, argument.GetLocation())); } if (Disposable.IsAssignedWithCreated(symbol, invocation, context.SemanticModel, context.CancellationToken).IsEither(Result.Yes, Result.AssumeYes) && !Disposable.IsDisposedBefore(symbol, invocation, context.SemanticModel, context.CancellationToken)) { context.ReportDiagnostic(Diagnostic.Create(IDISP003DisposeBeforeReassigning.Descriptor, argument.GetLocation())); } } } }
public static void ArrayFieldAssignedInCtor() { var code = @" namespace N { using System; internal class C { private IDisposable[] disposables = new IDisposable[1]; internal C(IDisposable disposable) { this.disposables[0] = disposable; } } }"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("IDisposable disposable"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual("N.C.disposables", container.ToString()); }
public static void WhenNotUsed(string type, string expression) { var code = @" namespace N { using System; internal class C { private readonly bool value; internal C(IDisposable disposable) { this.value = Equals(disposable, null); } } }".AssertReplace("bool", type) .AssertReplace("Equals(disposable, null)", expression); var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("IDisposable disposable"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(false, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out _)); }
public static void DisposableMixinsDisposeWith() { var code = @" namespace N { using System; using System.IO; using System.Reactive.Disposables; sealed class C : IDisposable { private readonly CompositeDisposable compositeDisposable = new CompositeDisposable(); public DisposeWith(Stream stream) { this.disposable = stream.DisposeWith(this.compositeDisposable); } public void Dispose() { this.compositeDisposable.Dispose(); } } }"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("Stream stream"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out _)); }
public static void ListOfTAddInInitializeParameter(string call) { var code = @" namespace N { using System; using System.Collections.Generic; internal class C { internal C(List<IDisposable> disposables, IDisposable disposable) { this.Initialize(disposables, disposable); } private void Initialize(List<IDisposable> disposables, IDisposable disposable) { disposables.Add(disposable); } } }".AssertReplace("this.Initialize(disposables, disposable)", call); var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("IDisposable disposable"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual(SymbolKind.Parameter, container.Kind); Assert.AreEqual("disposables", container.Name); }
public static void FieldAssignedViaCalledMethodParameter() { var code = @" namespace N { using System; internal class C { private IDisposable disposable; internal C(IDisposable disposable) { this.M(disposable); } private void M(IDisposable disposable) { this.disposable = disposable; } } }"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("IDisposable disposable"); var symbol = semanticModel.GetDeclaredSymbol(value, CancellationToken.None); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Assigns(localOrParameter, semanticModel, CancellationToken.None, out var field)); Assert.AreEqual("N.C.disposable", field.Symbol.ToString()); }
private static void Handle(SyntaxNodeAnalysisContext context) { if (!context.IsExcludedFromAnalysis() && context.Node is AssignmentExpressionSyntax { Left : { } left, Right : { } right } assignment&& !left.IsKind(SyntaxKind.ElementAccessExpression) && context.SemanticModel.TryGetSymbol(left, context.CancellationToken, out var assignedSymbol)) { if (LocalOrParameter.TryCreate(assignedSymbol, out var localOrParameter) && Disposable.IsCreation(right, context.SemanticModel, context.CancellationToken).IsEither(Result.Yes, Result.AssumeYes) && Disposable.ShouldDispose(localOrParameter, context.SemanticModel, context.CancellationToken)) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP001DisposeCreated, assignment.GetLocation())); } if (IsReassignedWithCreated(assignment, context)) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP003DisposeBeforeReassigning, assignment.GetLocation())); } if (assignedSymbol is IParameterSymbol { RefKind : RefKind.Ref } refParameter&& refParameter.ContainingSymbol.DeclaredAccessibility != Accessibility.Private && context.SemanticModel.TryGetType(right, context.CancellationToken, out var type) && Disposable.IsAssignableFrom(type, context.Compilation)) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP008DoNotMixInjectedAndCreatedForMember, context.Node.GetLocation())); } } }
private static void Handle(SyntaxNodeAnalysisContext context) { if (!context.IsExcludedFromAnalysis() && context.Node is ArgumentSyntax { Parent : ArgumentListSyntax { Parent : InvocationExpressionSyntax invocation } } argument&& argument.RefOrOutKeyword.IsEither(SyntaxKind.RefKeyword, SyntaxKind.OutKeyword) && IsCreation(argument, context.SemanticModel, context.CancellationToken) && context.SemanticModel.TryGetSymbol(argument.Expression, context.CancellationToken, out var symbol)) { if (symbol.Kind == SymbolKind.Discard || (LocalOrParameter.TryCreate(symbol, out var localOrParameter) && Disposable.ShouldDispose(localOrParameter, context.SemanticModel, context.CancellationToken))) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP001DisposeCreated, argument.GetLocation())); } if (Disposable.IsAssignedWithCreated(symbol, invocation, context.SemanticModel, context.CancellationToken).IsEither(Result.Yes, Result.AssumeYes) && !Disposable.IsDisposedBefore(symbol, invocation, context.SemanticModel, context.CancellationToken)) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP003DisposeBeforeReassigning, argument.GetLocation())); } } }
public static void InHttpClient(string expression, bool stores) { var code = @" namespace N { using System.Net.Http; public class C { private readonly IDisposable disposable; public C(HttpClientHandler handler) { this.disposable = new HttpClient(handler); } } }".AssertReplace("new HttpClient(handler)", expression); var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("handler"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(stores, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual(stores, Disposable.DisposedByReturnValue(syntaxTree.FindArgument("handler"), semanticModel, CancellationToken.None, out _)); if (stores) { Assert.AreEqual("N.C.disposable", container.ToString()); } }
public static void InDiscardedTuple(string expression) { var code = @" namespace N { using System; using System.Collections.Generic; internal class C { internal C(IDisposable disposable) { _ = Tuple.Create(disposable, 1); } } }".AssertReplace("Tuple.Create(disposable, 1)", expression); var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("IDisposable disposable"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(false, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out _)); }
internal static bool ShouldDispose(LocalOrParameter localOrParameter, ExpressionSyntax location, SemanticModel semanticModel, CancellationToken cancellationToken) { switch (localOrParameter.Symbol) { case ILocalSymbol local: return(ShouldDispose(local, location, semanticModel, cancellationToken)); case IParameterSymbol parameter: return(ShouldDispose(parameter, location, semanticModel, cancellationToken)); } throw new InvalidOperationException("Should never get here."); }
public static void CompositeDisposableExtAddAndReturn(string expression) { var code = @" namespace N { using System; using System.IO; using System.Reactive.Disposables; public static class CompositeDisposableExt { public static T AddAndReturn<T>(this CompositeDisposable disposable, T item) where T : IDisposable { if (item != null) { disposable.Add(item); } return item; } } public sealed class C : IDisposable { private readonly CompositeDisposable disposable = new CompositeDisposable(); public void Dispose() { this.disposable.Dispose(); } internal object M(Stream stream) { return this.disposable.AddAndReturn(stream); } } }".AssertReplace("disposable.AddAndReturn(stream)", expression); var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("Stream stream"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual("disposable", container.Name); Assert.AreEqual(SymbolKind.Parameter, container.Kind); }
internal static bool Returns(LocalOrParameter localOrParameter, SemanticModel semanticModel, CancellationToken cancellationToken) { using var recursion = Recursion.Borrow(localOrParameter.Symbol.ContainingType, semanticModel, cancellationToken); using var walker = UsagesWalker.Borrow(localOrParameter, recursion.SemanticModel, recursion.CancellationToken); foreach (var usage in walker.Usages) { if (Returns(usage, recursion)) { return(true); } } return(false); }
internal static bool Stores(LocalOrParameter localOrParameter, SemanticModel semanticModel, CancellationToken cancellationToken, [NotNullWhen(true)] out ISymbol?container) { using var recursion = Recursion.Borrow(localOrParameter.Symbol.ContainingType, semanticModel, cancellationToken); using var walker = UsagesWalker.Borrow(localOrParameter, semanticModel, cancellationToken); foreach (var usage in walker.Usages) { if (Stores(usage, recursion, out container)) { return(true); } } container = null; return(false); }
public static void InDisposingPairWhenFactoryMethod(string parameter) { var code = @" namespace N { using System; internal class C { private readonly Pair<IDisposable> pair; internal C(IDisposable disposable1, IDisposable disposable2) { this.pair = Create<IDisposable>(disposable1, disposable2); } private static Pair<T> Create<T>(T x, T y) where T : IDisposable => new Pair<T>(x, y); private class Pair<T> : IDisposable where T : IDisposable { private readonly T item1; private readonly T item2; public Pair(T item1, T item2) { this.item1 = item1; this.item2 = item2; } public void Dispose() { this.item1.Dispose(); (this.item2 as IDisposable)?.Dispose(); } } } }"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter(parameter); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual("N.C.pair", container.ToString()); }
public static void InPairWhenNew(string parameter) { var code = @" namespace N { using System; using System.Collections.Generic; internal class C { private readonly Pair<IDisposable> pair; internal C(IDisposable disposable1, IDisposable disposable2) { this.pair = new Pair<IDisposable>(disposable1, disposable2); } public class Pair<T> { public Pair(T item1, T item2) { this.Item1 = item1; this.Item2 = item2; } public T Item1 { get; } public T Item2 { get; } } } }"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter(parameter); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(true, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container)); Assert.AreEqual("N.C.pair", container.ToString()); }
internal static bool ShouldDispose(LocalOrParameter localOrParameter, SemanticModel semanticModel, CancellationToken cancellationToken) { if (localOrParameter.Symbol is IParameterSymbol parameter && parameter.RefKind != RefKind.None) { return(false); } using var recursion = Recursion.Borrow(localOrParameter.Symbol.ContainingType, semanticModel, cancellationToken); using var walker = UsagesWalker.Borrow(localOrParameter, semanticModel, cancellationToken); foreach (var usage in walker.Usages) { if (Returns(usage, recursion)) { return(false); } if (Assigns(usage, recursion, out _)) { return(false); } if (Stores(usage, recursion, out _)) { return(false); } if (Disposes(usage, recursion)) { return(false); } if (DisposedByReturnValue(usage, recursion, out _)) { return(false); } } return(true); }
public static void WhenNotUsed() { var code = @" namespace N { using System; internal class C { internal C(IDisposable disposable) { } } }"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var value = syntaxTree.FindParameter("IDisposable disposable"); Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol)); Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter)); Assert.AreEqual(false, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out _)); }
internal static bool Assigns(LocalOrParameter localOrParameter, SemanticModel semanticModel, CancellationToken cancellationToken, out FieldOrProperty first) { if (localOrParameter.TryGetScope(cancellationToken, out var scope) && localOrParameter is { ContainingSymbol : { ContainingType : { } containingType } })