Skip to content

Commit 54415ad

Browse files
authored
Merge pull request #22380 from yoff/yoff-fix-shared-cfg-ssa-regressions
Python: fix shared CFG exception-handler reachability
2 parents 7fda929 + a92b6a8 commit 54415ad

4 files changed

Lines changed: 51 additions & 9 deletions

File tree

python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,19 +1626,24 @@ private module Input implements InputSig1, InputSig2 {
16261626
n = any(Ast::AssertStmt a).getTest()
16271627
}
16281628

1629+
predicate postOrInOrder(Ast::AstNode n) { mayThrow(n) }
1630+
16291631
private string assertThrowTag() { result = "[assert-throw]" }
16301632

16311633
/**
16321634
* Holds if the expression node `e` may raise an exception at runtime as part of
16331635
* its normal evaluation (not via an explicit `raise`/`assert`, which are
16341636
* modeled separately).
16351637
*
1636-
* The set mirrors what the legacy CFG used to flag implicitly: function
1638+
* `TypeError` is intentionally excluded along with exceptions thrown by dunder
1639+
* implementations to avoid cluttering the CFG; we expect user code to catch
1640+
* these only rarely.
1641+
*
1642+
* Otherwise, the set mirrors what the legacy CFG used to flag implicitly: function
16371643
* calls (anything can raise), attribute access (`AttributeError`),
1638-
* subscript access (`IndexError`/`KeyError`/`TypeError`), arithmetic and
1639-
* comparison operators (`TypeError`/`ZeroDivisionError`), imports
1644+
* subscript access (`IndexError`/`KeyError`), division (`ZeroDivisionError`), imports
16401645
* (`ImportError`/`ModuleNotFoundError`), and generator/coroutine
1641-
* suspension points (`await`/`yield`/`yield from`).
1646+
* suspension points (`await`/`yield`/`yield from` throwing `CancelledError` or `GeneratorExit`).
16421647
*
16431648
* Bare `Name` reads are intentionally excluded — modeling every name
16441649
* read as `mayThrow` would explode CFG edge count for negligible
@@ -1652,11 +1657,7 @@ private module Input implements InputSig1, InputSig2 {
16521657
or
16531658
e instanceof Py::Subscript
16541659
or
1655-
e instanceof Py::BinaryExpr
1656-
or
1657-
e instanceof Py::UnaryExpr
1658-
or
1659-
e instanceof Py::Compare
1660+
e.(Py::BinaryExpr).getOp() instanceof Py::Div
16601661
or
16611662
e instanceof Py::ImportExpr
16621663
or

python/ql/test/library-tests/ControlFlow/shared-cfg-exceptions/ExceptionReachabilityTest.expected

Whitespace-only changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Inline-expectations test for exception-handler reachability in the shared CFG.
3+
*/
4+
5+
import python
6+
import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl
7+
import semmle.python.controlflow.internal.Cfg as Cfg
8+
import utils.test.InlineExpectationsTest
9+
10+
module ExceptionReachabilityTest implements TestSig {
11+
string getARelevantTag() { result = "exception-handler" }
12+
13+
predicate hasActualResult(Location location, string element, string tag, string value) {
14+
exists(
15+
Expr source, ExceptStmt handler, Cfg::ControlFlowNode sourceCfg,
16+
Cfg::ControlFlowNode handlerEntry
17+
|
18+
sourceCfg.getNode() = source and
19+
handlerEntry = sourceCfg.getAnExceptionalSuccessor() and
20+
CfgImpl::astNodeToPyNode(handlerEntry.getAstNode()) = handler and
21+
location = source.getLocation() and
22+
element = source.toString() and
23+
tag = "exception-handler" and
24+
value = handler.getType().toString()
25+
)
26+
}
27+
}
28+
29+
import MakeTest<ExceptionReachabilityTest>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
def generator():
2+
try:
3+
yield # $ exception-handler=GeneratorExit
4+
except GeneratorExit:
5+
return
6+
7+
8+
def load_module():
9+
try:
10+
import unavailable_module # $ exception-handler=ImportError
11+
except ImportError:
12+
return None

0 commit comments

Comments
 (0)