Skip to content

Commit f6962a2

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 c1e2d9f commit f6962a2

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

2157+
/** Holds if this exception type may be raised at control flow node `r`. */
2158+
predicate isRaisedAt(ControlFlowNode r) {
2159+
exists(Expr raised |
2160+
raised = r.getNode().(Raise).getRaised() and
2161+
this.getAUse().asExpr() in [raised, raised.(Call).getFunc()]
2162+
)
2163+
or
2164+
exists(Function callee |
2165+
resolveCall(r, callee, _) and
2166+
this.isRaisedIn(callee)
2167+
)
2168+
}
2169+
2170+
/**
2171+
* Holds if this exception type may be raised in function `f`, either
2172+
* directly via `raise` statements or transitively through calls to other functions.
2173+
*/
2174+
predicate isRaisedIn(Function f) { this.isRaisedAt(any(ControlFlowNode r | r.getScope() = f)) }
2175+
2176+
/** Holds if this exception type is handled by the `except` clause at `handler`. */
2177+
predicate isHandledAt(ExceptFlowNode handler) {
2178+
exists(ExceptStmt ex, Expr typeExpr | ex = handler.getNode() |
2179+
(
2180+
typeExpr = ex.getType()
2181+
or
2182+
typeExpr = ex.getType().(Tuple).getAnElt()
2183+
) and
2184+
this.getAUse().asExpr() = typeExpr
2185+
)
2186+
or
2187+
// A bare `except:` handles everything
2188+
not exists(handler.getNode().(ExceptStmt).getType()) and
2189+
this.(BuiltinExceptType).getName() = "BaseException"
2190+
}
2191+
21572192
/**
21582193
* Holds if this element is at the specified location.
21592194
* The location spans column `startColumn` of line `startLine` to
@@ -2222,5 +2257,128 @@ module ExceptionTypes {
22222257
endColumn = 0
22232258
}
22242259
}
2260+
2261+
/**
2262+
* Holds if the exception edge from `r` to `handler` is unlikely because
2263+
* none of the exception types that `r` may raise are handled by `handler`.
2264+
*/
2265+
predicate unlikelyExceptionEdge(ControlFlowNode r, ExceptFlowNode handler) {
2266+
handler = r.getAnExceptionalSuccessor() and
2267+
// We can determine at least one raised type
2268+
exists(ExceptType t | t.isRaisedAt(r)) and
2269+
// But none of them are handled by this handler
2270+
not exists(ExceptType raised, ExceptType handled |
2271+
raised.isRaisedAt(r) and
2272+
handled.isHandledAt(handler) and
2273+
raised.getADirectSuperclass*() = handled
2274+
)
2275+
}
22252276
}
22262277

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

0 commit comments

Comments
 (0)