Skip to content

Commit a97bbdd

Browse files
committed
python: rework hierarchy to avoid breaking API
1 parent 997e359 commit a97bbdd

File tree

8 files changed

+53
-17
lines changed

8 files changed

+53
-17
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
---
22
category: breaking
33
---
4-
* `Try.getAHandler()` and `Try.getHandler(<index>)` have results of type `Stmt` instead of `ExceptStmt`, as handlers may also be `ExceptGroupStmt`s (After Python 3.11 introduced PEP 654). This means that code of the form `try.getAHandler().getType()` will no longer work. Instead, use `try.getANormalHandler().getType()` or `try.getAGroupHandler().getType()`, depending on your use case.
4+
* The result of `Try::getAHandler` and `Try.getHandler(<index>)` is no longer of type `ExceptStmt`, as handlers may also be `ExceptGroupStmt`s (After Python 3.11 introduced PEP 654). Instead, it is of the new type `ExceptionHandler` of which `ExceptStmt` and `ExceptGroupStmt` are subtypes. To support selecting only one type of handler, `try.getANormalHandler` and `try.getAGroupHandler` have been added. Existing uses of `Try::getAHandler` for which it is important to select only normal handlers, will need to be updated to `try.getANormalHandler`.

python/ql/lib/semmle/python/AstGenerated.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1379,7 +1379,7 @@ class Try_ extends @py_Try, Stmt {
13791379
Stmt getHandler(int index) { result = this.getHandlers().getItem(index) }
13801380

13811381
/** Gets an exception handler of this try statement. */
1382-
Stmt getAHandler() { result = this.getHandlers().getAnItem() }
1382+
ExceptionHandler getAHandler() { result = this.getHandlers().getAnItem() }
13831383

13841384
/** Gets the finally block of this try statement. */
13851385
StmtList getFinalbody() { py_stmt_lists(result, this, 4) }

python/ql/lib/semmle/python/Stmts.qll

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,46 @@ class Exec extends Exec_ {
143143
override Stmt getASubStatement() { none() }
144144
}
145145

146-
/** An except group statement (part of a `try` statement), such as `except* IOError as err:` */
147-
class ExceptGroupStmt extends ExceptGroupStmt_ {
146+
/**
147+
* An exception handler such as an `except` or an `except*` statement
148+
* in a `try` statement.
149+
*
150+
* We do not wish to expose an abstract class that users are not
151+
* meant to extend, so this class is kept private. The user facing
152+
* class is `ExceptionHandler`.
153+
*/
154+
abstract private class ExceptionHandlerImpl extends Stmt {
155+
/** Gets the name of this except group block. */
156+
abstract Expr getName();
157+
158+
/** Gets the type of this except group block. */
159+
abstract Expr getType();
160+
}
161+
162+
/**
163+
* An exception handler such as an `except` or an `except*` statement
164+
* in a `try` statement.
165+
*/
166+
class ExceptionHandler extends Stmt instanceof ExceptionHandlerImpl {
167+
ExceptionHandler() {
168+
this instanceof ExceptStmt_
169+
or
170+
this instanceof ExceptGroupStmt_
171+
}
172+
148173
/** Gets the immediately enclosing try statement */
149174
Try getTry() { result.getAHandler() = this }
150175

176+
/** Gets the name of this except group block. */
177+
Expr getName() { result = super.getName() }
178+
179+
/** Gets the type of this except group block. */
180+
Expr getType() { result = super.getType() }
181+
}
182+
183+
/** An except group statement (part of a `try` statement), such as `except* IOError as err:` */
184+
class ExceptGroupStmt extends ExceptGroupStmt_, ExceptionHandlerImpl {
185+
/* syntax: except Expr [ as Expr ]: */
151186
override Expr getASubExpression() {
152187
result = this.getName()
153188
or
@@ -158,19 +193,18 @@ class ExceptGroupStmt extends ExceptGroupStmt_ {
158193

159194
override Stmt getLastStatement() { result = this.getBody().getLastItem().getLastStatement() }
160195

196+
override Expr getName() { result = ExceptGroupStmt_.super.getName() }
197+
161198
override Expr getType() {
162-
result = super.getType() and not result instanceof Tuple
199+
result = ExceptGroupStmt_.super.getType() and not result instanceof Tuple
163200
or
164-
result = super.getType().(Tuple).getAnElt()
201+
result = ExceptGroupStmt_.super.getType().(Tuple).getAnElt()
165202
}
166203
}
167204

168205
/** An except statement (part of a `try` statement), such as `except IOError as err:` */
169-
class ExceptStmt extends ExceptStmt_ {
206+
class ExceptStmt extends ExceptStmt_, ExceptionHandlerImpl {
170207
/* syntax: except Expr [ as Expr ]: */
171-
/** Gets the immediately enclosing try statement */
172-
Try getTry() { result.getAHandler() = this }
173-
174208
override Expr getASubExpression() {
175209
result = this.getName()
176210
or
@@ -181,10 +215,12 @@ class ExceptStmt extends ExceptStmt_ {
181215

182216
override Stmt getLastStatement() { result = this.getBody().getLastItem().getLastStatement() }
183217

218+
override Expr getName() { result = ExceptStmt_.super.getName() }
219+
184220
override Expr getType() {
185-
result = super.getType() and not result instanceof Tuple
221+
result = ExceptStmt_.super.getType() and not result instanceof Tuple
186222
or
187-
result = super.getType().(Tuple).getAnElt()
223+
result = ExceptStmt_.super.getType().(Tuple).getAnElt()
188224
}
189225
}
190226

python/ql/src/Exceptions/UnguardedNextInGenerator.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ predicate iter_not_exhausted(EssaVariable iterator) {
5252
predicate stop_iteration_handled(CallNode call) {
5353
exists(Try t |
5454
t.containsInScope(call.getNode()) and
55-
t.getANormalHandler().getType() = stopIteration().getAValueReachableFromSource().asExpr()
55+
t.getAHandler().getType() = stopIteration().getAValueReachableFromSource().asExpr()
5656
)
5757
}
5858

python/ql/src/Expressions/HashedButNoHash.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origi
7070
predicate typeerror_is_caught(ControlFlowNode f) {
7171
exists(Try try |
7272
try.getBody().contains(f.getNode()) and
73-
try.getANormalHandler().getType().pointsTo(ClassValue::typeError())
73+
try.getAHandler().getType().pointsTo(ClassValue::typeError())
7474
)
7575
}
7676

python/ql/src/Imports/DeprecatedModule.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ from ImportExpr imp, string name, string instead
7878
where
7979
name = imp.getName() and
8080
deprecated_module(name, instead, _, _) and
81-
not exists(Try try, ExceptStmt except | except = try.getANormalHandler() |
81+
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
8282
except.getType().pointsTo(ClassValue::importError()) and
8383
except.containsInScope(imp)
8484
)

python/ql/src/Variables/UndefinedGlobal.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import semmle.python.pointsto.PointsTo
1717

1818
predicate guarded_against_name_error(Name u) {
1919
exists(Try t | t.getBody().getAnItem().contains(u) |
20-
t.getANormalHandler().getType().(Name).getId() = "NameError"
20+
t.getAHandler().getType().(Name).getId() = "NameError"
2121
)
2222
or
2323
exists(ConditionBlock guard, BasicBlock controlled, Call globals |

python/ql/src/Variables/UninitializedLocal.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ predicate uninitialized_local(NameNode use) {
2727
predicate explicitly_guarded(NameNode u) {
2828
exists(Try t |
2929
t.getBody().contains(u.getNode()) and
30-
t.getANormalHandler().getType().pointsTo(ClassValue::nameError())
30+
t.getAHandler().getType().pointsTo(ClassValue::nameError())
3131
)
3232
}
3333

0 commit comments

Comments
 (0)