/** * @name Missing Dispose call on local IDisposable * @description Methods that create objects of type 'IDisposable' should call 'Dispose' on those * objects, otherwise unmanaged resources may not be released. * @kind problem * @problem.severity warning * @precision high * @id cs/local-not-disposed * @tags efficiency * maintainability * security * external/cwe/cwe-404 * external/cwe/cwe-459 * external/cwe/cwe-460 */ import csharp import Dispose import semmle.code.csharp.frameworks.System import semmle.code.csharp.commons.Disposal /** Holds if expression `e` escapes the local method scope. */ predicate escapes(Expr e) { // Things that return escape exists(Callable c | c.canReturn(e) or c.canYieldReturn(e)) or // Things that are assigned to fields, properties, or indexers escape the scope exists(AssignableDefinition def, Assignable a | def.getSource() = e and a = def.getTarget() | a instanceof Field or a instanceof Property or a instanceof Indexer ) or // Things that are added to a collection of some kind are likely to escape the scope exists(MethodCall mc | mc.getTarget().hasName("Add") | mc.getAnArgument() = e ) } /** Holds if the `disposable` is a whitelisted result. */ predicate isWhitelisted(LocalScopeDisposableCreation disposable) { exists(MethodCall mc | // Close can often be used instead of Dispose mc.getTarget().hasName("Close") or // Used as an alias for Dispose mc.getTarget().hasName("Clear") | mc.getQualifier() = disposable.getADisposeTarget() ) or // WebControls are usually disposed automatically disposable.getType() instanceof WebControl } from LocalScopeDisposableCreation disposable // The disposable is local scope - the lifetime is the execution of this method where not escapes(disposable.getADisposeTarget()) // Only care about library types - user types often have spurious IDisposable declarations and disposable.getType().fromLibrary() // Disposables constructed in the initializer of a `using` are safe and not exists(UsingStmt us | us.getAnExpr() = disposable.getADisposeTarget()) // Foreach calls Dispose and not exists(ForeachStmt fs | fs.getIterableExpr() = disposable.getADisposeTarget()) // As are disposables on which the Dispose method is called explicitly and not exists(MethodCall mc | mc.getTarget() instanceof DisposeMethod and mc.getQualifier() = disposable.getADisposeTarget() ) // Ignore whitelisted results and not isWhitelisted(disposable) // Not passed to a disposing method and not exists(Call c, Parameter p | disposable.getADisposeTarget() = c.getArgumentForParameter(p) | mayBeDisposed(p) ) select disposable, "Disposable '" + disposable.getType() + "' is created here but is not disposed."