mirror of
https://github.com/github/codeql.git
synced 2026-03-01 05:13:41 +01:00
150 lines
4.7 KiB
Plaintext
150 lines
4.7 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 quality
|
|
* reliability
|
|
* error-handling
|
|
* performance
|
|
* external/cwe/cwe-404
|
|
* external/cwe/cwe-459
|
|
* external/cwe/cwe-460
|
|
*/
|
|
|
|
import csharp
|
|
import Dispose
|
|
import semmle.code.csharp.frameworks.System
|
|
|
|
private class DisposeCall extends MethodCall {
|
|
DisposeCall() { this.getTarget() instanceof DisposeMethod }
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate isDisposedAccess(AssignableRead ar) {
|
|
exists(AssignableDefinition def, UsingStmt us |
|
|
ar = def.getAFirstRead() and
|
|
def.getTargetAccess() = us.getAVariableDeclExpr().getAccess()
|
|
)
|
|
or
|
|
exists(AssignableRead mid |
|
|
isDisposedAccess(mid) and
|
|
ar = mid.getANextRead()
|
|
)
|
|
}
|
|
|
|
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
|
DataFlow::localFlowStep(nodeFrom, nodeTo) and
|
|
not isDisposedAccess(nodeTo.asExpr())
|
|
}
|
|
|
|
private predicate reachesDisposeCall(DisposeCall disposeCall, DataFlow::Node node) {
|
|
localFlowStep(node, DataFlow::exprNode(disposeCall.getQualifier()))
|
|
or
|
|
exists(DataFlow::Node mid | reachesDisposeCall(disposeCall, mid) | localFlowStep(node, mid))
|
|
}
|
|
|
|
/**
|
|
* Holds if `disposeCall` disposes the object created by `disposableCreation`.
|
|
*/
|
|
predicate disposeReachableFromDisposableCreation(DisposeCall 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
|
|
reachesDisposeCall(disposeCall, DataFlow::exprNode(disposableCreation))
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
)
|
|
}
|
|
|
|
ControlFlowElement getACatchOrFinallyClauseChild() {
|
|
exists(TryStmt ts | result = ts.getACatchClause() or result = ts.getFinally())
|
|
or
|
|
result = getACatchOrFinallyClauseChild().getAChild()
|
|
}
|
|
|
|
private predicate candidate(DisposeCall disposeCall, Call call, Expr disposableCreation) {
|
|
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() = call and
|
|
call.getAReachableElement() = disposeCall
|
|
}
|
|
|
|
private class RelevantMethod extends Method {
|
|
RelevantMethod() {
|
|
exists(Call call |
|
|
candidate(_, call, _) and
|
|
this = call.getARuntimeTarget()
|
|
)
|
|
or
|
|
exists(RelevantMethod other | other.calls(this))
|
|
}
|
|
|
|
pragma[noinline]
|
|
private RelevantMethod callsNoTry() {
|
|
exists(MethodCall mc |
|
|
result = mc.getARuntimeTarget() and
|
|
not isTriedAgainstException(mc, _) and
|
|
mc.getEnclosingCallable() = this
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private RelevantMethod callsInTry(MethodCall mc) {
|
|
result = mc.getARuntimeTarget() and
|
|
isTriedAgainstException(mc, _) and
|
|
mc.getEnclosingCallable() = this
|
|
}
|
|
|
|
/**
|
|
* Gets an exception type that may be thrown during the execution of this method.
|
|
* Assumes any exception may be thrown by library types.
|
|
*/
|
|
Class getAThrownException() {
|
|
this.fromLibrary() and
|
|
result instanceof SystemExceptionClass
|
|
or
|
|
exists(ControlFlowElement cfe |
|
|
result = cfe.(ThrowElement).getExpr().getType() and
|
|
cfe.getEnclosingCallable() = this
|
|
or
|
|
result = this.callsInTry(cfe).getAThrownException()
|
|
|
|
|
not isTriedAgainstException(cfe, result)
|
|
)
|
|
or
|
|
result = this.callsNoTry().getAThrownException()
|
|
}
|
|
}
|
|
|
|
class MethodCallThatMayThrow extends MethodCall {
|
|
MethodCallThatMayThrow() {
|
|
exists(this.getARuntimeTarget().(RelevantMethod).getAThrownException())
|
|
}
|
|
}
|
|
|
|
from DisposeCall disposeCall, Expr disposableCreation, MethodCallThatMayThrow callThatThrows
|
|
where candidate(disposeCall, callThatThrows, disposableCreation)
|
|
select disposeCall, "Dispose missed if exception is thrown by $@.", callThatThrows,
|
|
callThatThrows.toString()
|