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>
This commit is contained in:
yoff
2026-06-02 13:30:51 +00:00
committed by yoff
parent 8179bffe64
commit 9e697386ef

View File

@@ -224,6 +224,13 @@ signature module AstSig<LocationSig Location> {
*/
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<LocationSig Location, AstSig<Location> 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<LocationSig Location, AstSig<Location> 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<LocationSig Location, AstSig<Location> 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())