Skip to content

Commit 8e99548

Browse files
committed
Shared: allow destructuring in ForeachStmt
1 parent 9f488f6 commit 8e99548

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
*
@@ -697,6 +723,8 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
697723

698724
private string patternMatchTrueTag() { result = "[MatchTrue]" }
699725

726+
private string foreachElementTag() { result = "[ForeachElement]" }
727+
700728
/**
701729
* Holds if an additional node tagged with `tag` should be created for
702730
* `n`. Edges targeting such nodes are labeled with `t` and therefore `t`
@@ -716,6 +744,10 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
716744
tag = loopHeaderTag() and
717745
t instanceof DirectSuccessor
718746
or
747+
foreachHasElementNode(n) and
748+
tag = foreachElementTag() and
749+
t instanceof DirectSuccessor
750+
or
719751
n instanceof PatternMatchExpr and
720752
tag = patternMatchTrueTag() and
721753
t.(BooleanSuccessor).getValue() = true
@@ -1785,7 +1817,21 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
17851817
n2.isAfter(loopstmt)
17861818
)
17871819
or
1788-
exists(ForeachStmt foreachstmt |
1820+
exists(ForeachStmt foreachstmt, PreControlFlowNode iterentry |
1821+
// `iterentry` is where each iteration begins, after the loop header
1822+
// (or after the collection is found to be non-empty): the element
1823+
// node if the language opts in, otherwise the loop variable, or the
1824+
// body directly if there is no variable.
1825+
foreachHasElementNode(foreachstmt) and
1826+
iterentry.isAdditional(foreachstmt, foreachElementTag())
1827+
or
1828+
not foreachHasElementNode(foreachstmt) and
1829+
(
1830+
iterentry.isBefore(foreachstmt.getVariable())
1831+
or
1832+
not exists(foreachstmt.getVariable()) and iterentry.isBefore(foreachstmt.getBody())
1833+
)
1834+
|
17891835
n1.isBefore(foreachstmt) and
17901836
n2.isBefore(foreachstmt.getCollection())
17911837
or
@@ -1799,8 +1845,14 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
17991845
or
18001846
n1.isAfterValue(foreachstmt.getCollection(),
18011847
any(EmptinessSuccessor t | t.getValue() = false)) and
1802-
n2.isBefore(foreachstmt.getVariable())
1848+
n2 = iterentry
18031849
or
1850+
n1.isAdditional(foreachstmt, loopHeaderTag()) and
1851+
n2 = iterentry
1852+
or
1853+
// After the loop variable, enter the body. When the language opts in
1854+
// to the element node, it is instead responsible for wiring the
1855+
// element node through to the body itself.
18041856
n1.isAfter(foreachstmt.getVariable()) and
18051857
n2.isBefore(foreachstmt.getBody())
18061858
or
@@ -1813,9 +1865,6 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
18131865
or
18141866
not exists(getLoopElse(foreachstmt)) and n2.isAfter(foreachstmt)
18151867
)
1816-
or
1817-
n1.isAdditional(foreachstmt, loopHeaderTag()) and
1818-
n2.isBefore(foreachstmt.getVariable())
18191868
)
18201869
or
18211870
exists(ForStmt forstmt, PreControlFlowNode condentry |

0 commit comments

Comments
 (0)