mirror of
https://github.com/github/codeql.git
synced 2026-06-03 04:40:14 +02:00
Shared CFG: add defaulted getWhileElse/getForeachElse to AstSig
Lifts the shared-controlflow changes out of the larger Python new-CFG PR so they can be reviewed independently. No behavioural change for existing languages (Java, C#, Ruby, Swift, Go, ...) — both new predicates default to `none()` and the Make0 loop-edge case extensions only fire when a language overrides them. Python's upcoming AstNodeImpl wiring uses these predicates to model `while-else` and `for-else`, where the `else` block runs when the loop condition becomes false (rather than via a `break`). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
4
shared/controlflow/change-notes/2026-05-19-loop-else.md
Normal file
4
shared/controlflow/change-notes/2026-05-19-loop-else.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* The `AstSig` signature gains two new defaulted predicates `getWhileElse` and `getForeachElse`, allowing languages (like Python) to model `while-else` / `for-else` constructs where the `else` branch is taken when the loop condition becomes false (rather than via a `break`). Existing languages that do not provide these predicates retain the previous behaviour.
|
||||
@@ -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. */
|
||||
@@ -1549,19 +1563,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
|
||||
@@ -1574,10 +1601,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