Skip to content

Commit ddfcfc9

Browse files
alexrfordhvitved
authored andcommitted
Desugar for loops as each calls
1 parent f6d99dc commit ddfcfc9

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed

ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,3 +816,90 @@ private module ArrayLiteralDesugar {
816816
final override predicate constantReadAccess(string name) { name = "::Array" }
817817
}
818818
}
819+
820+
/**
821+
* ```rb
822+
* for x in xs
823+
* <loop_body>
824+
* end
825+
* ```
826+
* desugars to, roughly,
827+
* ```rb
828+
* xs.each { |__synth__0| x = __synth__0; <loop_body> }
829+
* ```
830+
*
831+
* Note that for-loops, unlike blocks, do not create a new variable scope, so
832+
* variables within this block inherit the enclosing scope. The exception to
833+
* this is the synthesized variable declared by the block parameter, which is
834+
* scoped to the synthesized block.
835+
*/
836+
private module ForLoopDesugar {
837+
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
838+
exists(ForExpr for |
839+
// each call
840+
parent = for and
841+
i = -1 and
842+
child = SynthChild(MethodCallKind("each", false, 0))
843+
or
844+
exists(MethodCall eachCall | eachCall = TMethodCallSynth(for, -1, "each", false, 0) |
845+
// receiver
846+
parent = eachCall and
847+
i = 0 and
848+
child = RealChild(for.getValue()) // value is the Enumerable
849+
or
850+
parent = eachCall and
851+
i = -2 and
852+
child = SynthChild(BlockKind())
853+
or
854+
exists(Block block | block = TBlockSynth(eachCall, -2) |
855+
// block params
856+
parent = block and
857+
i = 0 and
858+
child = SynthChild(SimpleParameterKind())
859+
or
860+
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
861+
parent = param and
862+
i = 0 and
863+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
864+
or
865+
// assignment to pattern from for loop to synth parameter
866+
parent = block and
867+
i = 1 and
868+
child = SynthChild(AssignExprKind())
869+
or
870+
parent = TAssignExprSynth(block, 1) and
871+
(
872+
i = 0 and
873+
child = RealChild(for.getPattern())
874+
or
875+
i = 1 and
876+
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
877+
)
878+
)
879+
or
880+
// rest of block body
881+
parent = block and
882+
i = 2 and
883+
child = RealChild(for.getBody())
884+
)
885+
)
886+
)
887+
}
888+
889+
private class ForLoopSynthesis extends Synthesis {
890+
final override predicate child(AstNode parent, int i, Child child) {
891+
forLoopSynthesis(parent, i, child)
892+
}
893+
894+
final override predicate methodCall(string name, boolean setter, int arity) {
895+
name = "each" and
896+
setter = false and
897+
arity = 0
898+
}
899+
900+
final override predicate localVariable(AstNode n, int i) {
901+
n instanceof TSimpleParameterSynth and
902+
i = 0
903+
}
904+
}
905+
}

0 commit comments

Comments
 (0)