Skip to content

Commit 1cf58f7

Browse files
committed
Shared: allow destructuring in ForeachStmt
1 parent b8c78fd commit 1cf58f7

1 file changed

Lines changed: 55 additions & 6 deletions

File tree

shared/controlflow/codeql/controlflow/ControlFlowGraph.qll

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,39 @@ signature module AstSig<LocationSig Location> {
148148

149149
/** A for-loop that iterates over the elements of a collection. */
150150
class ForeachStmt extends LoopStmt {
151-
/** Gets the variable declaration of this `foreach` loop. */
151+
/**
152+
* Gets the variable declaration of this `foreach` loop, if any.
153+
*
154+
* A `foreach` loop that binds more than one variable per iteration (for
155+
* example when destructuring is used, as in `for k, v := range m` in Go, or
156+
* `for (a, b) in ...` in Rust/Python/Swift) should instead opt in to the
157+
* synthesized per-iteration element node (see `foreachHasElementNode`) and
158+
* destructure that element into its variables, in which case this predicate
159+
* need not have a result.
160+
*/
152161
Expr getVariable();
153162

154163
/** Gets the collection expression of this `foreach` loop. */
155164
Expr getCollection();
156165
}
157166

167+
/**
168+
* Holds if `foreach` has a synthesized per-iteration "element" node, that is,
169+
* an additional control flow node representing the element produced by each
170+
* iteration of the loop.
171+
*
172+
* When this holds, the shared library routes control flow from the loop
173+
* header (and from the non-empty collection) into the element node, but the
174+
* language is then responsible for wiring control flow out of the element
175+
* node and on to the loop body (typically destructuring the element into the
176+
* loop variables). In this mode the shared `getVariable` routing is not used.
177+
*
178+
* This is useful for languages whose loop variables are bound by extracting
179+
* components from an implicit "current element" value (as opposed to being
180+
* evaluated as ordinary target expressions).
181+
*/
182+
default predicate foreachHasElementNode(ForeachStmt foreach) { none() }
183+
158184
/**
159185
* A `break` statement.
160186
*
@@ -684,6 +710,8 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
684710

685711
private string patternMatchTrueTag() { result = "[MatchTrue]" }
686712

713+
private string foreachElementTag() { result = "[ForeachElement]" }
714+
687715
/**
688716
* Holds if an additional node tagged with `tag` should be created for
689717
* `n`. Edges targeting such nodes are labeled with `t` and therefore `t`
@@ -703,6 +731,10 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
703731
tag = loopHeaderTag() and
704732
t instanceof DirectSuccessor
705733
or
734+
foreachHasElementNode(n) and
735+
tag = foreachElementTag() and
736+
t instanceof DirectSuccessor
737+
or
706738
n instanceof PatternMatchExpr and
707739
tag = patternMatchTrueTag() and
708740
t.(BooleanSuccessor).getValue() = true
@@ -1640,7 +1672,21 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
16401672
n2.isAfter(loopstmt)
16411673
)
16421674
or
1643-
exists(ForeachStmt foreachstmt |
1675+
exists(ForeachStmt foreachstmt, PreControlFlowNode iterentry |
1676+
// `iterentry` is where each iteration begins, after the loop header
1677+
// (or after the collection is found to be non-empty): the element
1678+
// node if the language opts in, otherwise the loop variable, or the
1679+
// body directly if there is no variable.
1680+
foreachHasElementNode(foreachstmt) and
1681+
iterentry.isAdditional(foreachstmt, foreachElementTag())
1682+
or
1683+
not foreachHasElementNode(foreachstmt) and
1684+
(
1685+
iterentry.isBefore(foreachstmt.getVariable())
1686+
or
1687+
not exists(foreachstmt.getVariable()) and iterentry.isBefore(foreachstmt.getBody())
1688+
)
1689+
|
16441690
n1.isBefore(foreachstmt) and
16451691
n2.isBefore(foreachstmt.getCollection())
16461692
or
@@ -1654,8 +1700,14 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
16541700
or
16551701
n1.isAfterValue(foreachstmt.getCollection(),
16561702
any(EmptinessSuccessor t | t.getValue() = false)) and
1657-
n2.isBefore(foreachstmt.getVariable())
1703+
n2 = iterentry
16581704
or
1705+
n1.isAdditional(foreachstmt, loopHeaderTag()) and
1706+
n2 = iterentry
1707+
or
1708+
// After the loop variable, enter the body. When the language opts in
1709+
// to the element node, it is instead responsible for wiring the
1710+
// element node through to the body itself.
16591711
n1.isAfter(foreachstmt.getVariable()) and
16601712
n2.isBefore(foreachstmt.getBody())
16611713
or
@@ -1668,9 +1720,6 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
16681720
or
16691721
not exists(getLoopElse(foreachstmt)) and n2.isAfter(foreachstmt)
16701722
)
1671-
or
1672-
n1.isAdditional(foreachstmt, loopHeaderTag()) and
1673-
n2.isBefore(foreachstmt.getVariable())
16741723
)
16751724
or
16761725
exists(ForStmt forstmt, PreControlFlowNode condentry |

0 commit comments

Comments
 (0)