Skip to content

Commit 676c740

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 e14d493 commit 676c740

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
@@ -2199,6 +2199,41 @@ module ExceptionTypes {
21992199
/** Gets a string representation of this exception type. */
22002200
string toString() { result = this.getName() }
22012201

2202+
/** Holds if this exception type may be raised at control flow node `r`. */
2203+
predicate isRaisedAt(ControlFlowNode r) {
2204+
exists(Expr raised |
2205+
raised = r.getNode().(Raise).getRaised() and
2206+
this.getAUse().asExpr() in [raised, raised.(Call).getFunc()]
2207+
)
2208+
or
2209+
exists(Function callee |
2210+
resolveCall(r, callee, _) and
2211+
this.isRaisedIn(callee)
2212+
)
2213+
}
2214+
2215+
/**
2216+
* Holds if this exception type may be raised in function `f`, either
2217+
* directly via `raise` statements or transitively through calls to other functions.
2218+
*/
2219+
predicate isRaisedIn(Function f) { this.isRaisedAt(any(ControlFlowNode r | r.getScope() = f)) }
2220+
2221+
/** Holds if this exception type is handled by the `except` clause at `handler`. */
2222+
predicate isHandledAt(ExceptFlowNode handler) {
2223+
exists(ExceptStmt ex, Expr typeExpr | ex = handler.getNode() |
2224+
(
2225+
typeExpr = ex.getType()
2226+
or
2227+
typeExpr = ex.getType().(Tuple).getAnElt()
2228+
) and
2229+
this.getAUse().asExpr() = typeExpr
2230+
)
2231+
or
2232+
// A bare `except:` handles everything
2233+
not exists(handler.getNode().(ExceptStmt).getType()) and
2234+
this.(BuiltinExceptType).getName() = "BaseException"
2235+
}
2236+
22022237
/**
22032238
* Holds if this element is at the specified location.
22042239
* The location spans column `startColumn` of line `startLine` to
@@ -2267,5 +2302,128 @@ module ExceptionTypes {
22672302
endColumn = 0
22682303
}
22692304
}
2305+
2306+
/**
2307+
* Holds if the exception edge from `r` to `handler` is unlikely because
2308+
* none of the exception types that `r` may raise are handled by `handler`.
2309+
*/
2310+
predicate unlikelyExceptionEdge(ControlFlowNode r, ExceptFlowNode handler) {
2311+
handler = r.getAnExceptionalSuccessor() and
2312+
// We can determine at least one raised type
2313+
exists(ExceptType t | t.isRaisedAt(r)) and
2314+
// But none of them are handled by this handler
2315+
not exists(ExceptType raised, ExceptType handled |
2316+
raised.isRaisedAt(r) and
2317+
handled.isHandledAt(handler) and
2318+
raised.getADirectSuperclass*() = handled
2319+
)
2320+
}
22702321
}
22712322

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

0 commit comments

Comments
 (0)