mirror of
https://github.com/github/codeql.git
synced 2026-05-14 11:19:27 +02:00
Shared CFG: support for-else and while-else loops
Add two default predicates to AstSig:
default AstNode getWhileElse(WhileStmt loop) { none() }
default AstNode getForeachElse(ForeachStmt loop) { none() }
When defined, the explicit-step rules for While/Do and Foreach
route the loop's normal-completion exits through the else block
before reaching the after-loop node:
- WhileStmt: after-false condition -> before-else -> after-while
(instead of directly after-while).
- ForeachStmt: after-collection [empty] and the LoopHeader exit
are both routed through before-else -> after-foreach.
Python's Ast module overrides the predicates to return the
synthetic BlockStmt for the orelse slot, replacing the previous
customisations in Input::step. This eliminates parallel direct
successors emitted by the previous Python-side step additions
(verified: multipleSuccessors on a CPython database goes from
1340 to 0).
Java and C# CFG tests are unaffected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -411,6 +411,16 @@ module Ast implements AstSig<Py::Location> {
|
||||
*/
|
||||
AstNode getTryElse(TryStmt try) { result = try.getElse() }
|
||||
|
||||
/**
|
||||
* Gets the `else` branch of `while` loop `loop`, if any.
|
||||
*/
|
||||
AstNode getWhileElse(WhileStmt loop) { result = loop.getElse() }
|
||||
|
||||
/**
|
||||
* Gets the `else` branch of `for` loop `loop`, if any.
|
||||
*/
|
||||
AstNode getForeachElse(ForeachStmt loop) { result = loop.getElse() }
|
||||
|
||||
/** An exception handler (`except` or `except*`). */
|
||||
class CatchClause extends Stmt {
|
||||
private Py::ExceptionHandler handler;
|
||||
@@ -1182,29 +1192,6 @@ private module Input implements InputSig1, InputSig2 {
|
||||
n1.isAfter(assertStmt.getMsg()) and
|
||||
n2.isAdditional(assertStmt, assertThrowTag())
|
||||
)
|
||||
or
|
||||
// While/else: when the condition is false, flow to the else block
|
||||
// (if present) before the after-while node.
|
||||
exists(Ast::WhileStmt w, Ast::BlockStmt orelse | orelse = w.getElse() |
|
||||
n1.isAfterFalse(w.getCondition()) and
|
||||
n2.isBefore(orelse)
|
||||
or
|
||||
n1.isAfter(orelse) and
|
||||
n2.isAfter(w)
|
||||
)
|
||||
or
|
||||
// For/else: when the collection is empty or the loop completes
|
||||
// normally, flow through the else block before the after-for node.
|
||||
exists(Ast::ForeachStmt f, Ast::BlockStmt orelse | orelse = f.getElse() |
|
||||
n1.isAfterValue(f.getCollection(), any(EmptinessSuccessor t | t.getValue() = true)) and
|
||||
n2.isBefore(orelse)
|
||||
or
|
||||
n1.isAfter(f.getBody()) and
|
||||
n2.isBefore(orelse)
|
||||
or
|
||||
n1.isAfter(orelse) and
|
||||
n2.isAfter(f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
| test_loops.py:53:12:53:38 | After Compare | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
| test_loops.py:53:12:53:38 | After Compare | Timestamp 13 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
| test_loops.py:53:12:53:38 | After Compare | Timestamp 13 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
| test_loops.py:53:12:53:38 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
| test_loops.py:53:12:53:38 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
| test_loops.py:54:13:54:40 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
| test_loops.py:54:13:54:40 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
| test_loops.py:54:13:54:40 | After Compare | Timestamp 16 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:51:1:51:29 | Function test_while_else_break | test_while_else_break |
|
||||
@@ -158,8 +156,6 @@
|
||||
| test_loops.py:111:14:111:43 | After List | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:111:14:111:43 | After List | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:111:14:111:43 | After List | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:111:14:111:43 | After List | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:111:14:111:43 | After List | Timestamp 11 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:112:13:112:38 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:112:13:112:38 | After Compare | Timestamp 7 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:112:13:112:38 | After Compare | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
@@ -168,8 +164,6 @@
|
||||
| test_loops.py:114:9:114:9 | x | Timestamp 4 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:114:9:114:9 | x | Timestamp 8 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:114:9:114:9 | x | Timestamp 8 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:114:9:114:9 | x | Timestamp 11 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:114:9:114:9 | x | Timestamp 11 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:110:1:110:27 | Function test_for_else_break | test_for_else_break |
|
||||
| test_loops.py:123:14:123:33 | After List | Timestamp 21 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops |
|
||||
| test_loops.py:123:14:123:33 | After List | Timestamp 21 on true/false branch is missing a dead() annotation on the true successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops |
|
||||
| test_loops.py:124:18:124:47 | After List | Timestamp 3 on true/false branch is missing a dead() annotation on the false successor in $@ | test_loops.py:122:1:122:25 | Function test_nested_loops | test_nested_loops |
|
||||
|
||||
@@ -211,6 +211,20 @@ signature module AstSig<LocationSig Location> {
|
||||
*/
|
||||
default AstNode getTryElse(TryStmt try) { none() }
|
||||
|
||||
/**
|
||||
* Gets the `else` block of this `while` loop statement, if any.
|
||||
*
|
||||
* Only some languages (e.g. Python) support `while-else` constructs.
|
||||
*/
|
||||
default AstNode getWhileElse(WhileStmt loop) { none() }
|
||||
|
||||
/**
|
||||
* Gets the `else` block of this `foreach` loop statement, if any.
|
||||
*
|
||||
* Only some languages (e.g. Python) support `for-else` constructs.
|
||||
*/
|
||||
default AstNode getForeachElse(ForeachStmt loop) { none() }
|
||||
|
||||
/** A catch clause in a try statement. */
|
||||
class CatchClause extends AstNode {
|
||||
/** Gets the variable declared by this catch clause. */
|
||||
@@ -1539,19 +1553,32 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
|
||||
n2.isBefore(loopstmt.getBody())
|
||||
or
|
||||
n1.isAfterFalse(cond) and
|
||||
n2.isAfter(loopstmt)
|
||||
(
|
||||
n2.isBefore(getWhileElse(loopstmt))
|
||||
or
|
||||
not exists(getWhileElse(loopstmt)) and n2.isAfter(loopstmt)
|
||||
)
|
||||
or
|
||||
n1.isAfter(loopstmt.getBody()) and
|
||||
n2.isAdditional(loopstmt, loopHeaderTag())
|
||||
)
|
||||
or
|
||||
exists(WhileStmt whilestmt |
|
||||
n1.isAfter(getWhileElse(whilestmt)) and
|
||||
n2.isAfter(whilestmt)
|
||||
)
|
||||
or
|
||||
exists(ForeachStmt foreachstmt |
|
||||
n1.isBefore(foreachstmt) and
|
||||
n2.isBefore(foreachstmt.getCollection())
|
||||
or
|
||||
n1.isAfterValue(foreachstmt.getCollection(),
|
||||
any(EmptinessSuccessor t | t.getValue() = true)) and
|
||||
n2.isAfter(foreachstmt)
|
||||
(
|
||||
n2.isBefore(getForeachElse(foreachstmt))
|
||||
or
|
||||
not exists(getForeachElse(foreachstmt)) and n2.isAfter(foreachstmt)
|
||||
)
|
||||
or
|
||||
n1.isAfterValue(foreachstmt.getCollection(),
|
||||
any(EmptinessSuccessor t | t.getValue() = false)) and
|
||||
@@ -1564,10 +1591,17 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
|
||||
n2.isAdditional(foreachstmt, loopHeaderTag())
|
||||
or
|
||||
n1.isAdditional(foreachstmt, loopHeaderTag()) and
|
||||
n2.isAfter(foreachstmt)
|
||||
(
|
||||
n2.isBefore(getForeachElse(foreachstmt))
|
||||
or
|
||||
not exists(getForeachElse(foreachstmt)) and n2.isAfter(foreachstmt)
|
||||
)
|
||||
or
|
||||
n1.isAdditional(foreachstmt, loopHeaderTag()) and
|
||||
n2.isBefore(foreachstmt.getVariable())
|
||||
or
|
||||
n1.isAfter(getForeachElse(foreachstmt)) and
|
||||
n2.isAfter(foreachstmt)
|
||||
)
|
||||
or
|
||||
exists(ForStmt forstmt, PreControlFlowNode condentry |
|
||||
|
||||
Reference in New Issue
Block a user