From 9e697386efeb9e35c25b904e1e6a704ec62cd568 Mon Sep 17 00:00:00 2001 From: yoff <51428+yoff@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:30:51 +0000 Subject: [PATCH] Shared CFG: add defaulted getLoopElse to AstSig Adds a new defaulted signature predicates to the shared CFG library: - getLoopElse: `else` block of a loop statement, if any (used by Python's `while-else` / `for-else` constructs). The predicate defaults to `none()`, so behaviour is unchanged for any language that doesn't override it (verified by re-running java/ql/test/library-tests/controlflow/). The Make0 succession rules are extended: - WhileStmt/ForeachStmt: route the loop-exit edge through the else block before reaching the after-position. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../codeql/controlflow/ControlFlowGraph.qll | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll index 71d22c416ea..c0cb83debb8 100644 --- a/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll +++ b/shared/controlflow/codeql/controlflow/ControlFlowGraph.qll @@ -224,6 +224,13 @@ signature module AstSig { */ default AstNode getTryElse(TryStmt try) { none() } + /** + * Gets the `else` block of loop statement `loop`, if any. + * + * Only some languages (e.g. Python) support `for-else` constructs. + */ + default AstNode getLoopElse(LoopStmt loop) { none() } + /** A catch clause in a try statement. */ class CatchClause extends AstNode { /** Gets the variable declared by this catch clause. */ @@ -1578,10 +1585,17 @@ module Make0 Ast> { n2.isBefore(loopstmt.getBody()) or n1.isAfterValue(cond, any(BooleanSuccessor b | b.getValue() = while.booleanNot())) and - n2.isAfter(loopstmt) + ( + n2.isBefore(getLoopElse(loopstmt)) + or + not exists(getLoopElse(loopstmt)) and n2.isAfter(loopstmt) + ) or n1.isAfter(loopstmt.getBody()) and n2.isAdditional(loopstmt, loopHeaderTag()) + or + n1.isAfter(getLoopElse(loopstmt)) and + n2.isAfter(loopstmt) ) or exists(ForeachStmt foreachstmt | @@ -1590,7 +1604,11 @@ module Make0 Ast> { or n1.isAfterValue(foreachstmt.getCollection(), any(EmptinessSuccessor t | t.getValue() = true)) and - n2.isAfter(foreachstmt) + ( + n2.isBefore(getLoopElse(foreachstmt)) + or + not exists(getLoopElse(foreachstmt)) and n2.isAfter(foreachstmt) + ) or n1.isAfterValue(foreachstmt.getCollection(), any(EmptinessSuccessor t | t.getValue() = false)) and @@ -1603,7 +1621,11 @@ module Make0 Ast> { n2.isAdditional(foreachstmt, loopHeaderTag()) or n1.isAdditional(foreachstmt, loopHeaderTag()) and - n2.isAfter(foreachstmt) + ( + n2.isBefore(getLoopElse(foreachstmt)) + or + not exists(getLoopElse(foreachstmt)) and n2.isAfter(foreachstmt) + ) or n1.isAdditional(foreachstmt, loopHeaderTag()) and n2.isBefore(foreachstmt.getVariable())