Skip to content

Commit 7d8b4ac

Browse files
committed
Python: Add Reachability module
The implementation is essentially the same as the one from `BasicBlockWithPointsTo`, with the main difference being that this one uses the exception machinery we just added (and some extensions added in this commit).
1 parent f5361f4 commit 7d8b4ac

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,6 +2175,41 @@ module ExceptionTypes {
21752175
/** Gets a string representation of this exception type. */
21762176
string toString() { result = this.getName() }
21772177

2178+
/** Holds if this exception type may be raised at control flow node `r`. */
2179+
predicate isRaisedAt(ControlFlowNode r) {
2180+
exists(Expr raised |
2181+
raised = r.getNode().(Raise).getRaised() and
2182+
this.getAUse().asExpr() in [raised, raised.(Call).getFunc()]
2183+
)
2184+
or
2185+
exists(Function callee |
2186+
resolveCall(r, callee, _) and
2187+
this.isRaisedIn(callee)
2188+
)
2189+
}
2190+
2191+
/**
2192+
* Holds if this exception type may be raised in function `f`, either
2193+
* directly via `raise` statements or transitively through calls to other functions.
2194+
*/
2195+
predicate isRaisedIn(Function f) { this.isRaisedAt(any(ControlFlowNode r | r.getScope() = f)) }
2196+
2197+
/** Holds if this exception type is handled by the `except` clause at `handler`. */
2198+
predicate isHandledAt(ExceptFlowNode handler) {
2199+
exists(ExceptStmt ex, Expr typeExpr | ex = handler.getNode() |
2200+
(
2201+
typeExpr = ex.getType()
2202+
or
2203+
typeExpr = ex.getType().(Tuple).getAnElt()
2204+
) and
2205+
this.getAUse().asExpr() = typeExpr
2206+
)
2207+
or
2208+
// A bare `except:` handles everything
2209+
not exists(handler.getNode().(ExceptStmt).getType()) and
2210+
this.(BuiltinExceptType).getName() = "BaseException"
2211+
}
2212+
21782213
/**
21792214
* Holds if this element is at the specified location.
21802215
* The location spans column `startColumn` of line `startLine` to
@@ -2243,5 +2278,128 @@ module ExceptionTypes {
22432278
endColumn = 0
22442279
}
22452280
}
2281+
2282+
/**
2283+
* Holds if the exception edge from `r` to `handler` is unlikely because
2284+
* none of the exception types that `r` may raise are handled by `handler`.
2285+
*/
2286+
predicate unlikelyExceptionEdge(ControlFlowNode r, ExceptFlowNode handler) {
2287+
handler = r.getAnExceptionalSuccessor() and
2288+
// We can determine at least one raised type
2289+
exists(ExceptType t | t.isRaisedAt(r)) and
2290+
// But none of them are handled by this handler
2291+
not exists(ExceptType raised, ExceptType handled |
2292+
raised.isRaisedAt(r) and
2293+
handled.isHandledAt(handler) and
2294+
raised.getADirectSuperclass*() = handled
2295+
)
2296+
}
22462297
}
22472298

2299+
/**
2300+
* Provides predicates for reasoning about the reachability of control flow nodes
2301+
* and basic blocks.
2302+
*/
2303+
module Reachability {
2304+
private import semmle.python.ApiGraphs
2305+
import ExceptionTypes
2306+
2307+
/**
2308+
* Holds if `call` is a call to a function that is known to never return normally
2309+
* (e.g. `sys.exit()`, `os._exit()`, `os.abort()`).
2310+
*/
2311+
predicate isCallToNeverReturningFunction(CallNode call) {
2312+
// Known never-returning builtins/stdlib functions via API graphs
2313+
call = API::builtin("exit").getACall().asCfgNode()
2314+
or
2315+
call = API::builtin("quit").getACall().asCfgNode()
2316+
or
2317+
call = API::moduleImport("sys").getMember("exit").getACall().asCfgNode()
2318+
or
2319+
call = API::moduleImport("os").getMember("_exit").getACall().asCfgNode()
2320+
or
2321+
call = API::moduleImport("os").getMember("abort").getACall().asCfgNode()
2322+
or
2323+
// User-defined functions that only contain raise statements (no normal returns)
2324+
exists(Function target |
2325+
resolveCall(call, target, _) and
2326+
neverReturns(target)
2327+
)
2328+
}
2329+
2330+
/**
2331+
* Holds if function `f` never returns normally, because every normal exit
2332+
* is dominated by a call to a never-returning function or an unconditional raise.
2333+
*/
2334+
predicate neverReturns(Function f) {
2335+
exists(f.getANormalExit()) and
2336+
forall(BasicBlock exit | exit = f.getANormalExit().getBasicBlock() |
2337+
exists(BasicBlock raising |
2338+
raising.dominates(exit) and
2339+
(
2340+
isCallToNeverReturningFunction(raising.getLastNode())
2341+
or
2342+
raising.getLastNode().getNode() instanceof Raise
2343+
)
2344+
)
2345+
)
2346+
}
2347+
2348+
/**
2349+
* Holds if it is highly unlikely for control to flow from `node` to `succ`.
2350+
*/
2351+
predicate unlikelySuccessor(ControlFlowNode node, ControlFlowNode succ) {
2352+
// Exceptional edge where the raised type doesn't match the handler
2353+
unlikelyExceptionEdge(node, succ)
2354+
or
2355+
// Normal successor of a never-returning call
2356+
isCallToNeverReturningFunction(node) and
2357+
succ = node.getASuccessor() and
2358+
not succ = node.getAnExceptionalSuccessor() and
2359+
not succ.getNode() instanceof Yield
2360+
}
2361+
2362+
private predicate startBbLikelyReachable(BasicBlock b) {
2363+
exists(Scope s | s.getEntryNode() = b.getNode(_))
2364+
or
2365+
exists(BasicBlock pred |
2366+
pred = b.getAPredecessor() and
2367+
endBbLikelyReachable(pred) and
2368+
not unlikelySuccessor(pred.getLastNode(), b)
2369+
)
2370+
}
2371+
2372+
private predicate endBbLikelyReachable(BasicBlock b) {
2373+
startBbLikelyReachable(b) and
2374+
not exists(ControlFlowNode p, ControlFlowNode s |
2375+
unlikelySuccessor(p, s) and
2376+
p = b.getNode(_) and
2377+
s = b.getNode(_) and
2378+
not p = b.getLastNode()
2379+
)
2380+
}
2381+
2382+
/**
2383+
* Holds if basic block `b` is likely to be reachable from the entry of its
2384+
* enclosing scope.
2385+
*/
2386+
predicate likelyReachable(BasicBlock b) { startBbLikelyReachable(b) }
2387+
2388+
/**
2389+
* Holds if it is unlikely that `node` can be reached during execution.
2390+
*/
2391+
predicate unlikelyReachable(ControlFlowNode node) {
2392+
not startBbLikelyReachable(node.getBasicBlock())
2393+
or
2394+
exists(BasicBlock b |
2395+
startBbLikelyReachable(b) and
2396+
not endBbLikelyReachable(b) and
2397+
exists(ControlFlowNode p, int i, int j |
2398+
unlikelySuccessor(p, _) and
2399+
p = b.getNode(i) and
2400+
node = b.getNode(j) and
2401+
i < j
2402+
)
2403+
)
2404+
}
2405+
}

0 commit comments

Comments
 (0)