Files
codeql/csharp/ql/src/API Abuse/DisposeNotCalledOnException.ql

88 lines
3.1 KiB
Plaintext

/**
* @name Dispose may not be called if an exception is thrown during execution
* @description Methods that create objects of type 'IDisposable' should call 'Dispose()' on those
* objects, even during exceptional circumstances, otherwise unmanaged resources may
* not be released.
* @kind problem
* @problem.severity warning
* @precision medium
* @id cs/dispose-not-called-on-throw
* @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
/**
* Gets an exception type that may be thrown during the execution of method `m`.
* Assumes any exception may be thrown by library types.
*/
Class getAThrownException(Method m) {
m.fromLibrary() and
result = any(SystemExceptionClass sc)
or
exists(ControlFlowElement cfe |
cfe = any(ThrowElement te | result = te.getExpr().getType()) or
cfe = any(MethodCall mc | result = getAThrownException(mc.getARuntimeTarget()))
|
cfe.getEnclosingCallable() = m and
not isTriedAgainstException(cfe, result)
)
}
/**
* Holds if control flow element is tried against throwing an exception of type
* `ec`.
*/
pragma[noinline]
predicate isTriedAgainstException(ControlFlowElement cfe, ExceptionClass ec) {
(cfe instanceof ThrowElement or cfe instanceof MethodCall) and
exists(TryStmt ts |
ts.getATriedElement() = cfe and
exists(ts.getAnExceptionHandler(ec))
)
}
/**
* Holds if `disposeCall` disposes the object created by `disposableCreation`.
*/
predicate disposeReachableFromDisposableCreation(MethodCall disposeCall, Expr disposableCreation) {
// The qualifier of the Dispose call flows from something that introduced a disposable into scope
(
disposableCreation instanceof LocalScopeDisposableCreation or
disposableCreation instanceof MethodCall
) and
DataFlow::localFlowStep+(DataFlow::exprNode(disposableCreation),
DataFlow::exprNode(disposeCall.getQualifier())) and
disposeCall.getTarget() instanceof DisposeMethod
}
class MethodCallThatMayThrow extends MethodCall {
MethodCallThatMayThrow() { exists(getAThrownException(this.getARuntimeTarget())) }
}
ControlFlowElement getACatchOrFinallyClauseChild() {
exists(TryStmt ts | result = ts.getACatchClause() or result = ts.getFinally())
or
result = getACatchOrFinallyClauseChild().getAChild()
}
from MethodCall disposeCall, Expr disposableCreation, MethodCallThatMayThrow callThatThrows
where
disposeReachableFromDisposableCreation(disposeCall, disposableCreation) and
// The dispose call is not, itself, within a dispose method.
not disposeCall.getEnclosingCallable() instanceof DisposeMethod and
// Dispose call not within a finally or catch block
not getACatchOrFinallyClauseChild() = disposeCall and
// At least one method call exists between the allocation and disposal that could throw
disposableCreation.getAReachableElement() = callThatThrows and
callThatThrows.getAReachableElement() = disposeCall
select disposeCall, "Dispose missed if exception is thrown by $@.", callThatThrows,
callThatThrows.toString()