Repository: joernio/joern (scala)

Issue:
[Bug] jssrc No dataflow in for ... in loop
Currently, there seems to be missing dataflow in a for ... in loop while a forEach loop gives the expected dataflow. Using the queries below gives no results for the given example code.
```cpg.identifier("m_elem").reachableByFlows(cpg.method("myFunction").parameter.order(1)).p``` 
```cpg.identifier("m_elem").reachableByFlows(cpg.identifier("message")).p```

```
importCode.javascript.fromString("""function myFunction(message) {
         for(var m_elem in message){
             console.log(m_elem)
         }
     }""")
```

However, using the parameter with order 0 in the above query finds the following dataflow using the "this" parameter:
```
  """_____________________________________________________________________________
| nodeType          | tracked              | lineNumber| method     | file   |
|============================================================================|
| MethodParameterIn | myFunction(this, ... | 1         | myFunction | tmp.js |
| Identifier        | Object.keys(messa... | 2         | myFunction | tmp.js |
| Call              | Object.keys(messa... | 2         | myFunction | tmp.js |
| Identifier        | _iterator_0 = Obj... | 2         | myFunction | tmp.js |
| Identifier        | _iterator_0.next()   | 2         | myFunction | tmp.js |
| Call              | _iterator_0.next()   | 2         | myFunction | tmp.js |
| Identifier        | (_result_0 = _ite... | 2         | myFunction | tmp.js |
| Call              | m_elem = _result_... | 2         | myFunction | tmp.js |
| Identifier        | m_elem = _result_... | 2         | myFunction | tmp.js |
""",
  """_____________________________________________________________________________
| nodeType          | tracked              | lineNumber| method     | file   |
|============================================================================|
| MethodParameterIn | myFunction(this, ... | 1         | myFunction | tmp.js |
| Identifier        | Object.keys(messa... | 2         | myFunction | tmp.js |
| Call              | Object.keys(messa... | 2         | myFunction | tmp.js |
| Identifier        | _iterator_0 = Obj... | 2         | myFunction | tmp.js |
| Identifier        | _iterator_0.next()   | 2         | myFunction | tmp.js |
| Call              | _iterator_0.next()   | 2         | myFunction | tmp.js |
| Identifier        | (_result_0 = _ite... | 2         | myFunction | tmp.js |
| Call              | m_elem = _result_... | 2         | myFunction | tmp.js |
| Identifier        | m_elem = _result_... | 2         | myFunction | tmp.js |
| Identifier        | console.log(m_elem)  | 3         | myFunction | tmp.js |
"""
```
I then tried to write an example for a forEach loop:

```
importCode.javascript.fromString("""function myFunction(message) {
           Object.keys(message).forEach(m_elem => console.log(m_elem))
       }""")
```
Using the same queries of ```cpg.identifier("m_elem").reachableByFlows(cpg.method("myFunction").parameter.order(1)).p``` and ```cpg.identifier("m_elem").reachableByFlows(cpg.identifier("message")).p``` it was able to find the correct flow. However, using the parameter with order 0 in the above query finds no dataflow (which feels like the correct result), the complete opposite of the example with a for ... in loop.

Pull request description:
Fixes: https://github.com/joernio/joern/issues/2300

Patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForStatementsCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForStatementsCreator.scala
index bf2911c06b32..af0e4d639f65 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForStatementsCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForStatementsCreator.scala
@@ -281,8 +281,8 @@ trait AstForStatementsCreator { this: AstCreator =>
     *
     * to:
     *
-    * { var _iterator = Object.keys(arr)[Symbol.iterator](); var _result; var i; while (!(_result =
-    * _iterator.next()).done) { i = _result.value; body } }
+    * { var _iterator = <operator>.iterator(arr); var _result; var i; while (!(_result = _iterator.next()).done) { i =
+    * _result.value; body } }
     */
   protected def astForInOfStatement(forInOfStmt: BabelNodeInfo): Ast = {
     // surrounding block:
@@ -300,58 +300,27 @@ trait AstForStatementsCreator { this: AstCreator =>
     diffGraph.addEdge(localAstParentStack.head, iteratorLocalNode, EdgeTypes.AST)
     scope.addVariableReference(iteratorName, iteratorNode)
 
-    val callNode = createCallNode(
-      s"Object.keys($collectionName)[Symbol.iterator]()",
-      "",
-      DispatchTypes.DYNAMIC_DISPATCH,
-      forInOfStmt.lineNumber,
-      forInOfStmt.columnNumber
-    )
-
-    val thisNode = createIdentifierNode("this", forInOfStmt)
-
-    val indexCallNode = createCallNode(
-      s"Object.keys($collectionName)[Symbol.iterator]",
-      Operators.indexAccess,
+    val iteratorCall = createCallNode(
+      s"<operator>.iterator($collectionName)",
+      "<operator>.iterator", // TODO: add to schema
       DispatchTypes.STATIC_DISPATCH,
       forInOfStmt.lineNumber,
       forInOfStmt.columnNumber
     )
 
-    val objectKeysCallNode = createStaticCallNode(
-      s"Object.keys($collectionName)",
-      "keys",
-      "Object.keys",
-      forInOfStmt.lineNumber,
-      forInOfStmt.columnNumber
-    )
-
     val objectKeysCallArgs = List(astForNodeWithFunctionReference(collection))
-    val objectKeysCallAst  = callAst(objectKeysCallNode, objectKeysCallArgs)
-
-    val indexBaseNode = createIdentifierNode("Symbol", forInOfStmt)
-
-    val indexMemberNode = createFieldIdentifierNode("iterator", forInOfStmt.lineNumber, forInOfStmt.columnNumber)
-
-    val indexAccessNode =
-      createFieldAccessCallAst(indexBaseNode, indexMemberNode, forInOfStmt.lineNumber, forInOfStmt.columnNumber)
-
-    val indexCallArgs = List(objectKeysCallAst, indexAccessNode)
-    val indexCallAst  = callAst(indexCallNode, indexCallArgs)
-
-    val callNodeArgs = List(Ast(thisNode))
-    val callNodeAst  = callAst(callNode, callNodeArgs, receiver = Option(indexCallAst))
+    val objectKeysCallAst  = callAst(iteratorCall, objectKeysCallArgs)
 
     val iteratorAssignmentNode =
       createCallNode(
-        s"$iteratorName = Object.keys($collectionName)[Symbol.iterator]()",
+        s"$iteratorName = <operator>.iterator($collectionName)",
         Operators.assignment,
         DispatchTypes.STATIC_DISPATCH,
         forInOfStmt.lineNumber,
         forInOfStmt.columnNumber
       )
 
-    val iteratorAssignmentArgs = List(Ast(iteratorNode), callNodeAst)
+    val iteratorAssignmentArgs = List(Ast(iteratorNode), objectKeysCallAst)
     val iteratorAssignmentAst  = callAst(iteratorAssignmentNode, iteratorAssignmentArgs)
 
     // _result:
@@ -398,7 +367,7 @@ trait AstForStatementsCreator { this: AstCreator =>
 
     val rhsNode = createCallNode(
       s"$iteratorName.next()",
-      "",
+      "next",
       DispatchTypes.DYNAMIC_DISPATCH,
       forInOfStmt.lineNumber,
       forInOfStmt.columnNumber


Test patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/dataflow/DataflowTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/dataflow/DataflowTest.scala
index fa46ad7b494e..acd08eeaa2bc 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/dataflow/DataflowTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/dataflow/DataflowTest.scala
@@ -11,18 +11,17 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from function call read to multiple versions of the same variable" in {
     val cpg: Cpg = code("""
-        | function flows1(fd, mode) {
-        |     var buff = [];
-        |
-        |     var sz = 0;
-        |     if (mode == 1) sz = 20;
-        |     if (mode == 2) sz = 200;
-        |     if (mode == 3) sz = 41;
-        |     if (mode == 5) sz = -5;
-        |
-        |     read(fd, buff, sz);
-        | }
-      """.stripMargin)
+      |function flows1(fd, mode) {
+      |  var buff = [];
+      |
+      |  var sz = 0;
+      |  if (mode == 1) sz = 20;
+      |  if (mode == 2) sz = 200;
+      |  if (mode == 3) sz = 41;
+      |  if (mode == 5) sz = -5;
+      |
+      |  read(fd, buff, sz);
+      |}""".stripMargin)
 
     def source = cpg.identifier.name("sz")
     def sink   = cpg.call.code("read.*")
@@ -46,15 +45,14 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from function call argument" in {
     val cpg: Cpg = code("""
-        | function foo(x) {}
+        |function foo(x) {}
         |
-        | function method(y){
+        |function method(y) {
         |  var a = 10;
-        |  if (a < y){
+        |  if (a < y) {
         |    foo(a);
-        |  };
-        | }
-      """.stripMargin)
+        |  }
+        |}""".stripMargin)
 
     val source = cpg.identifier.name("a")
     val sink   = cpg.call.code("foo.*").argument
@@ -64,16 +62,15 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow chains from x to a" in {
     val cpg: Cpg = code("""
-        | function flow() {
-        |   var a = 0x37;
-        |   var b = a;
-        |   var c = 0x31;
-        |   var z = b + c;
-        |   z++;
-        |   var p = z;
-        |   var x = z;
-        | }
-      """.stripMargin)
+      |function flow() {
+      |  var a = 0x37;
+      |  var b = a;
+      |  var c = 0x31;
+      |  var z = b + c;
+      |  z++;
+      |  var p = z;
+      |  var x = z;
+      |}""".stripMargin)
 
     val source = cpg.identifier.name("a")
     val sink   = cpg.identifier.name("x")
@@ -88,13 +85,12 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from method return to a" in {
     val cpg: Cpg = code("""
-        | function flow(a){
-        |   var z = a;
-        |   var b = z;
-        |
-        |   return b;
-        | };
-      """.stripMargin)
+      |function flow(a) {
+      |  var z = a;
+      |  var b = z;
+      |
+      |  return b;
+      |}""".stripMargin)
 
     val source = cpg.identifier.name("a")
     val sink   = cpg.method(".*flow").ast.isReturn
@@ -106,21 +102,20 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow with nested if-statements from method return to a" in {
     val cpg: Cpg = code("""
-        | function nested(a){
-        |   var x = 0;
-        |   var z = 1;
-        |   if(a < 10){
-        |     if( a < 5){
-        |       if(a < 2){
-        |          x = a;
-        |       }
-        |     }
-        |   } else
-        |     x = z;
-        |
-        |   return x;
-        | }
-      """.stripMargin)
+      |function nested(a) {
+      |  var x = 0;
+      |  var z = 1;
+      |  if(a < 10) {
+      |    if( a < 5) {
+      |      if(a < 2) {
+      |        x = a;
+      |      }
+      |    }
+      |  } else
+      |    x = z;
+      |
+      |  return x;
+      |}""".stripMargin)
 
     val source = cpg.call.code("a < 10").argument.code("a")
     val sink   = cpg.method(".*nested").ast.isReturn
@@ -132,21 +127,20 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow with nested if-statements to `return x`" in {
     val cpg: Cpg = code("""
-        | function nested(a) {
-        |   var x = 0;
-        |   var z = 1;
-        |   if(a < 10){
-        |     if( a < 5){
-        |       if(a < 2){
-        |          x = a;
-        |       }
-        |     }
-        |   } else
-        |     x = z;
-        |
-        |   return x;
-        | };
-      """.stripMargin)
+      |function nested(a) {
+      |  var x = 0;
+      |  var z = 1;
+      |  if(a < 10) {
+      |    if( a < 5) {
+      |      if(a < 2) {
+      |        x = a;
+      |      }
+      |    }
+      |  } else
+      |    x = z;
+      |
+      |  return x;
+      |}""".stripMargin)
 
     val source = cpg.identifier.name("x")
     val sink   = cpg.method(".*nested").ast.isReturn
@@ -163,14 +157,13 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow chain from function argument of foo to a" in {
     val cpg: Cpg = code("""
-        | function foo(y) {};
-        |
-        | function param(x){
-        |    var a = x;
-        |    var b = a;
-        |    var z = foo(b);
-        |  }
-      """.stripMargin)
+      |function foo(y) {};
+      |
+      |function param(x) {
+      |  var a = x;
+      |  var b = a;
+      |  var z = foo(b);
+      |} """.stripMargin)
 
     val source = cpg.identifier.name("a")
     val sink   = cpg.call.code("foo.*").argument
@@ -180,12 +173,11 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from function foo to a" in {
     val cpg: Cpg = code("""
-        | function param(x){
-        |    var a = x;
-        |    var b = a;
-        |    var z = foo(b);
-        |  }
-      """.stripMargin)
+      |function param(x) {
+      |  var a = x;
+      |  var b = a;
+      |  var z = foo(b);
+      |}""".stripMargin)
 
     val source = cpg.identifier.name("a")
     val sink   = cpg.call.code("foo.*").argument(1)
@@ -197,17 +189,16 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow with member access in expression to identifier x" in {
     val cpg: Cpg = code("""
-        | var node = {
-        |  'value1' : 1,
-        |  'value2' : 2
-        | };
-        |
-        |function test(){
-        |  var x = 10;
-        |  node.value1 = x;
-        |  node.value2 = node.value1;
-        |}
-      """.stripMargin)
+      |var node = {
+      |  'value1' : 1,
+      |  'value2' : 2
+      |};
+      |
+      |function test() {
+      |  var x = 10;
+      |  node.value1 = x;
+      |  node.value2 = node.value1;
+      |}""".stripMargin)
 
     val source = cpg.identifier.name("x")
     val sink   = cpg.call.code("node.value2")
@@ -222,16 +213,15 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow chain from x to literal 37" in {
     val cpg: Cpg = code("""
-        | function flow() {
-        |   var a = 37;
-        |   var b = a;
-        |   var c = 31;
-        |   var z = b + c;
-        |   z++;
-        |   var p = z;
-        |   var x = z;
-        | }
-      """.stripMargin)
+      |function flow() {
+      |  var a = 37;
+      |  var b = a;
+      |  var c = 31;
+      |  var z = b + c;
+      |  z++;
+      |  var p = z;
+      |  var x = z;
+      |}""".stripMargin)
 
     val source = cpg.literal.code("37")
     val sink   = cpg.identifier.name("x")
@@ -243,13 +233,12 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow with short hand assignment operator" in {
     val cpg: Cpg = code("""
-        | function flow() {
-        |    var a = 37;
-        |    var b = a;
-        |    var z = b;
-        |    z += a;
-        | }
-       """.stripMargin)
+      |function flow() {
+      |  var a = 37;
+      |  var b = a;
+      |  var z = b;
+      |  z += a;
+      |}""".stripMargin)
 
     val source = cpg.call.code("var a = 37").argument(2)
     val sink   = cpg.call.code("z \\+= a").argument(1)
@@ -261,14 +250,13 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow after short hand assignment" in {
     val cpg: Cpg = code("""
-        | function flow() {
-        |    var a = 37;
-        |    var b = a;
-        |    var z = b;
-        |    z += a;
-        |    var w = z;
-        | }
-      """.stripMargin)
+      |function flow() {
+      |  var a = 37;
+      |  var b = a;
+      |  var z = b;
+      |  z += a;
+      |  var w = z;
+      |}""".stripMargin)
 
     val source = cpg.call.code("var a = 37").argument(1)
     val sink   = cpg.identifier.name("w")
@@ -280,13 +268,12 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from array method parameter to identifier" in {
     val cpg: Cpg = code("""
-        | function main(argc, argv){
-        |    var x = argv[1];
-        |    var y = x;
-        |    var z = y;
-        |    return 0;
-        | }
-      """.stripMargin)
+        |function main(argc, argv){
+        |  var x = argv[1];
+        |  var y = x;
+        |  var z = y;
+        |  return 0;
+        |}""".stripMargin)
 
     val source = cpg.method(".*main").parameter
     val sink   = cpg.identifier.name("y")
@@ -301,11 +288,10 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow for conditional expressions" in {
     val cpg: Cpg = code("""
-        |function foo(x, y) {
-        |  var z =  x ? f(y) : g(y);
-        |  return;
-        | }
-      """.stripMargin)
+      |function foo(x, y) {
+      |  var z =  x ? f(y) : g(y);
+      |  return;
+      |}""".stripMargin)
 
     val source = cpg.method.parameter.name("y")
     val sink   = cpg.identifier.name("z")
@@ -318,14 +304,14 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow for source in caller" in {
     val cpg: Cpg = code("""
-        |function bar() {
-        |  var x = source();
-        |  foo(x);
-        |}
-        |
-        |function foo(y) {
-        |  sink(y);
-        |}""".stripMargin)
+      |function bar() {
+      |  var x = source();
+      |  foo(x);
+      |}
+      |
+      |function foo(y) {
+      |  sink(y);
+      |}""".stripMargin)
 
     val source = cpg.call.code("source.*")
     val sink   = cpg.call.code("sink.*").argument
@@ -338,16 +324,16 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow for source in callee" in {
     val cpg: Cpg = code("""
-        |function bar() {
-        |  return source();
-        |}
-        |
-        |function sink(param) {}
-        |
-        |function foo(y) {
-        |  var y = bar();
-        |  sink(y);
-        |}""".stripMargin)
+      |function bar() {
+      |  return source();
+      |}
+      |
+      |function sink(param) {}
+      |
+      |function foo(y) {
+      |  var y = bar();
+      |  sink(y);
+      |}""".stripMargin)
 
     val source = cpg.call.code("source.*")
     val sink   = cpg.call.code("sink.*").argument(1)
@@ -360,16 +346,16 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow for using formal parameters as sink" in {
     val cpg: Cpg = code("""
-        |function bar() {
-        |  return source();
-        |}
-        |
-        |function sink(param) {}
-        |
-        |function foo(y) {
-        |  var y = bar();
-        |  sink(y);
-        |}""".stripMargin)
+      |function bar() {
+      |  return source();
+      |}
+      |
+      |function sink(param) {}
+      |
+      |function foo(y) {
+      |  var y = bar();
+      |  sink(y);
+      |}""".stripMargin)
 
     val source = cpg.call.code("source.*")
     val sink   = cpg.method(".*sink").parameter.index(1)
@@ -390,26 +376,25 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow for struct data" in {
     val cpg: Cpg = code("""
-        | var point = {
-        |   'x' : 0,
-        |   'y' : 0
-        | }
-        |
-        | function source() {
-        |   return 2.0;
-        | }
-        |
-        | function sink(x) {
-        |   return 3;
-        | }
-        |
-        | function main() {
-        |   var k = source();
-        |   point.x = k;
-        |   point.y = 2;
-        |   sink(point.x);
-        | }
-        |""".stripMargin)
+      |var point = {
+      |  'x' : 0,
+      |  'y' : 0
+      |}
+      |
+      |function source() {
+      |  return 2.0;
+      |}
+      |
+      |function sink(x) {
+      |  return 3;
+      |}
+      |
+      |function main() {
+      |  var k = source();
+      |  point.x = k;
+      |  point.y = 2;
+      |  sink(point.x);
+      |}""".stripMargin)
 
     val source = cpg.call.code("source.*")
     val sink   = cpg.call.code("sink.*").argument
@@ -423,13 +408,12 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow for object element access" in {
     val cpg: Cpg = code("""
-        | var s = { 'field' : 0 }
-        |
-        | function foo(arg) {
-        |   arg.field = source();
-        |   sink(arg.field);
-        | }
-        |""".stripMargin)
+      |var s = { 'field' : 0 }
+      |
+      |function foo(arg) {
+      |  arg.field = source();
+      |  sink(arg.field);
+      |}""".stripMargin)
 
     val source = cpg.call.code("source.*")
     val sink   = cpg.call.code("sink.*").argument
@@ -441,12 +425,10 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow for object element access passed to source" in {
     val cpg: Cpg = code("""
-        |function bar() {
-        |  source(a.b);
-        |  sink(a.b);
-        |}
-        |
-        |""".stripMargin)
+      |function bar() {
+      |  source(a.b);
+      |  sink(a.b);
+      |}""".stripMargin)
 
     val source = cpg.call.code("source.*").argument
     val sink   = cpg.call.code("sink.*").argument
@@ -457,11 +439,10 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flows for statements to METHOD_RETURN" in {
     val cpg: Cpg = code("""
-        |function foo(y, x) {
-        |  free(y);
-        |  free(x);
-        |}
-        |""".stripMargin)
+      |function foo(y, x) {
+      |  free(y);
+      |  free(x);
+      |}""".stripMargin)
 
     val source = cpg.call.code("free.*").argument(1)
     val sink   = cpg.method(".*foo").methodReturn
@@ -473,10 +454,9 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Should not create edges from call to ret twice" in {
     val cpg: Cpg = code("""
-        | function foo() {
-        |   return bar();
-        | }
-    """.stripMargin)
+      |function foo() {
+      |  return bar();
+      |}""".stripMargin)
 
     cpg.call
       .code("bar.*")
@@ -486,9 +466,9 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from outer params to inner params" in {
     val cpg: Cpg = code("""
-        | function f(x, y) {
-        |   g(x, y);
-        | }""".stripMargin)
+      |function f(x, y) {
+      |  g(x, y);
+      |}""".stripMargin)
 
     def source = cpg.method(".*f").parameter
     def sink   = cpg.call.code("g.*").argument
@@ -503,13 +483,13 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from non-static member to sink" in {
     val cpg: Cpg = code("""
-        |class Foo {
-        |  x = "foo";
-        |  func() {
-        |    sink(x);
-        |  }
-        |}
-        |""".stripMargin)
+      |class Foo {
+      |  x = "foo";
+      |  func() {
+      |    sink(x);
+      |  }
+      |}
+      |""".stripMargin)
 
     val sink   = cpg.call("sink").argument(1).l
     val source = cpg.member.name("x").l
@@ -520,13 +500,13 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from static member to sink" in {
     val cpg: Cpg = code("""
-        |class Foo {
-        |  static x = "foo";
-        |  func() {
-        |    sink(x);
-        |  }
-        |}
-        |""".stripMargin)
+      |class Foo {
+      |  static x = "foo";
+      |  func() {
+      |    sink(x);
+      |  }
+      |}
+      |""".stripMargin)
 
     val sink   = cpg.call("sink").argument(1).l
     val source = cpg.member.name("x").l
@@ -536,9 +516,7 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
   }
 
   "Flow from receiver to closure parameters" in {
-    val cpg: Cpg = code("""
-        |foo.bar( (x,y) => { sink1(x); sink2(y); } )
-        |""".stripMargin)
+    val cpg: Cpg = code("foo.bar( (x,y) => { sink1(x); sink2(y); } )")
 
     val sink = cpg.call("sink1").argument(1).l
     val src  = cpg.identifier.name("foo").l
@@ -546,9 +524,7 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
   }
 
   "Flow through constructor" in {
-    val cpg: Cpg = code("""
-        |const x = new Foo(y);
-        |""".stripMargin)
+    val cpg: Cpg = code("const x = new Foo(y);")
 
     val sink = cpg.identifier("x").l
     val src  = cpg.identifier("y").l
@@ -556,9 +532,7 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
   }
 
   "Flow through constructor and object notation" in {
-    val cpg: Cpg = code("""
-                          |const x = new Foo({ z : y } );
-                          |""".stripMargin)
+    val cpg: Cpg = code("const x = new Foo({ z : y } );")
 
     val sink = cpg.identifier("x").l
     val src  = cpg.identifier("y").l
@@ -566,9 +540,7 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
   }
 
   "Flow from field via object notation" in {
-    val cpg: Cpg = code("""
-                          |const x = { p : a.y } ;
-                          |""".stripMargin)
+    val cpg: Cpg = code("const x = { p : a.y };")
 
     val sink = cpg.identifier("x").l
     val src  = cpg.fieldAccess.where(_.fieldIdentifier.canonicalName("y")).l
@@ -579,9 +551,9 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow from inside object notation to call argument" in {
     val cpg: Cpg = code("""
-        |const a = { b : 47 } ;
-        |fn(a);
-        |""".stripMargin)
+      |const a = { b : 47 } ;
+      |fn(a);
+      |""".stripMargin)
 
     val sink = cpg.call.nameExact("fn")
     val src  = cpg.literal("47")
@@ -590,12 +562,11 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Flow into method defined as lambda and assigned to constant" in {
     val cpg: Cpg = code("""
-        | const foo = (x, y) => {
-        |   sink(x);
-        | };
-        |
-        | foo(1, 2);
-        |""".stripMargin)
+      |const foo = (x, y) => {
+      |  sink(x);
+      |};
+      |foo(1, 2);
+      |""".stripMargin)
 
     val sink = cpg.call("sink").l
     val src  = cpg.literal.code("1").l
@@ -606,10 +577,9 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
 
   "Should not reach irrelevant nodes" in {
     val cpg: Cpg = code("""
-        |const irrelevant = "irrelevant";
-        |const a = { } ;
-        |sink(a);
-        |""".stripMargin)
+      |const irrelevant = "irrelevant";
+      |const a = { } ;
+      |sink(a);""".stripMargin)
 
     val sink = cpg.call("sink").l
     val src  = cpg.literal("\"irrelevant\"").l
@@ -618,4 +588,34 @@ class DataflowTest extends DataFlowCodeToCpgSuite {
     sink.reachableBy(src).size shouldBe 0
   }
 
+  "Flow correctly through for-in loops" in {
+    val cpg: Cpg = code("""
+      |function foo(x) {
+      |  for(var elem in x) {
+      |    console.log(elem)
+      |  }
+      |}""".stripMargin)
+
+    val sink = cpg.identifier("elem").l
+    val src1 = cpg.method("foo").parameter.order(1).l
+    val src2 = cpg.identifier("x").l
+
+    // two flows because we find a second one in the lowered for-in loop code
+    sink.reachableBy(src1).size shouldBe 2
+    sink.reachableBy(src2).size shouldBe 2
+  }
+
+  "Flow correctly through for-each lambda" in {
+    val cpg: Cpg = code("""
+      |function foo(x) {
+      |  Object.keys(x).forEach(elem => console.log(elem))
+      |}""".stripMargin)
+
+    val sink = cpg.identifier("elem").l
+    val src1 = cpg.method("foo").parameter.order(1).l
+    val src2 = cpg.identifier("x").l
+    sink.reachableBy(src1).size shouldBe 1
+    sink.reachableBy(src2).size shouldBe 1
+  }
+
 }
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/SimpleAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/SimpleAstCreationPassTest.scala
index 46e0e3b6aaaf..e1c6115ca5b1 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/SimpleAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/SimpleAstCreationPassTest.scala
@@ -1482,7 +1482,7 @@ class SimpleAstCreationPassTest extends AbstractPassTest {
     localI.code shouldBe "i"
 
     val List(iteratorAssignment) =
-      node.astChildren.isCall.codeExact("_iterator_0 = Object.keys(arr)[Symbol.iterator]()").l
+      node.astChildren.isCall.codeExact("_iterator_0 = <operator>.iterator(arr)").l
     iteratorAssignment.name shouldBe Operators.assignment
 
     val List(iteratorAssignmentLhs) = iteratorAssignment.astChildren.isIdentifier.l
@@ -1491,34 +1491,17 @@ class SimpleAstCreationPassTest extends AbstractPassTest {
     iteratorAssignmentLhs.argumentIndex shouldBe 1
 
     val List(iteratorAssignmentRhs) = iteratorAssignment.astChildren.isCall.l
-    iteratorAssignmentRhs.code shouldBe "Object.keys(arr)[Symbol.iterator]()"
+    iteratorAssignmentRhs.code shouldBe "<operator>.iterator(arr)"
     iteratorAssignmentRhs.order shouldBe 2
     iteratorAssignmentRhs.argumentIndex shouldBe 2
+    iteratorAssignmentRhs.name shouldBe "<operator>.iterator"
+    iteratorAssignmentRhs.methodFullName shouldBe "<operator>.iterator"
+    iteratorAssignmentRhs.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
 
-    val List(indexCall) = iteratorAssignmentRhs.astChildren.isCall.l
-    indexCall.name shouldBe Operators.indexAccess
-
-    val List(objectKeysCall) = indexCall.astChildren.isCall.order(1).l
-    objectKeysCall.name shouldBe "keys"
-    objectKeysCall.methodFullName shouldBe "Object.keys"
-    objectKeysCall.code shouldBe "Object.keys(arr)"
-    objectKeysCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
-
-    val objectKeysCallArg = objectKeysCall.argument(1).asInstanceOf[Identifier]
+    val objectKeysCallArg = iteratorAssignmentRhs.argument(1).asInstanceOf[Identifier]
     objectKeysCallArg.name shouldBe "arr"
     objectKeysCallArg.order shouldBe 1
 
-    val List(indexAccessCall) = indexCall.astChildren.isCall.order(2).l
-    indexAccessCall.argumentIndex shouldBe 2
-
-    val List(symbolIdentifier) = indexAccessCall.astChildren.isIdentifier.order(1).l
-    symbolIdentifier.name shouldBe "Symbol"
-    symbolIdentifier.argumentIndex shouldBe 1
-
-    val List(iteratorIdentifier) = indexAccessCall.astChildren.isFieldIdentifier.order(2).l
-    iteratorIdentifier.canonicalName shouldBe "iterator"
-    iteratorIdentifier.argumentIndex shouldBe 2
-
     val List(varResult) = node.astChildren.isIdentifier.nameExact("_result_0").l
     varResult.code shouldBe "_result_0"
 
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/SimpleCfgCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/SimpleCfgCreationPassTest.scala
index af8670fa59f4..99db969fb9e2 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/SimpleCfgCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/SimpleCfgCreationPassTest.scala
@@ -522,20 +522,12 @@ class SimpleCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
     }
 
     "be correct for for-loop with for-in" in {
-      implicit val cpg: Cpg = code("""
-                                     |for (var i in arr) {
-                                     |   foo(i)
-                                     |}
-                                     |""".stripMargin)
+      implicit val cpg: Cpg = code("for (var i in arr) { foo(i) }")
       testForInOrOf()
     }
 
     "be correct for for-loop with for-of" in {
-      implicit val cpg: Cpg = code("""
-                                     |for (var i of arr) {
-                                     |   foo(i)
-                                     |}
-                                     |""".stripMargin)
+      implicit val cpg: Cpg = code("for (var i of arr) { foo(i) }")
       testForInOrOf()
     }
 
@@ -697,21 +689,10 @@ class SimpleCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
   private def testForInOrOf()(implicit cpg: Cpg): Unit = {
     succOf(":program") shouldBe expected(("_iterator_0", AlwaysEdge))
     succOf("_iterator_0") shouldBe expected(("arr", AlwaysEdge))
-    succOf("arr") shouldBe expected(("Object.keys(arr)", AlwaysEdge))
-    succOf("Object.keys(arr)") shouldBe expected(("Symbol", AlwaysEdge))
-    succOf("Symbol") shouldBe expected(("iterator", AlwaysEdge))
-    succOf("iterator") shouldBe expected(("Symbol.iterator", AlwaysEdge))
-    succOf("Symbol.iterator") shouldBe expected(("Object.keys(arr)[Symbol.iterator]", AlwaysEdge))
-    succOf("Object.keys(arr)[Symbol.iterator]") shouldBe expected(("this", AlwaysEdge))
-    succOf("this", NodeTypes.IDENTIFIER) shouldBe expected(("Object.keys(arr)[Symbol.iterator]()", AlwaysEdge))
-    succOf("Object.keys(arr)[Symbol.iterator]()") shouldBe expected(
-      ("_iterator_0 = Object.keys(arr)[Symbol.iterator]()", AlwaysEdge)
-    )
-
-    succOf("_iterator_0 = Object.keys(arr)[Symbol.iterator]()") shouldBe expected(("_result_0", AlwaysEdge))
-
+    succOf("arr") shouldBe expected(("<operator>.iterator(arr)", AlwaysEdge))
+    succOf("<operator>.iterator(arr)") shouldBe expected(("_iterator_0 = <operator>.iterator(arr)", AlwaysEdge))
+    succOf("_iterator_0 = <operator>.iterator(arr)") shouldBe expected(("_result_0", AlwaysEdge))
     succOf("_result_0") shouldBe expected(("i", AlwaysEdge))
-
     succOf("i") shouldBe expected(("_result_0", 1, AlwaysEdge))
     succOf("_result_0", 1) shouldBe expected(("_iterator_0", 1, AlwaysEdge))
     succOf("_iterator_0", 1) shouldBe expected(("next", AlwaysEdge))
@@ -736,9 +717,9 @@ class SimpleCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
     succOf("_result_0.value") shouldBe expected(("i = _result_0.value", AlwaysEdge))
     succOf("i = _result_0.value") shouldBe expected(("foo", AlwaysEdge))
     succOf("foo") shouldBe expected(("this", 1, AlwaysEdge))
-    succOf("this", 2) shouldBe expected(("i", 2, AlwaysEdge))
+    succOf("this", 1) shouldBe expected(("i", 2, AlwaysEdge))
     succOf("i", 2) shouldBe expected(("foo(i)", AlwaysEdge))
-    val code2 = "{" + "\n" + "   foo(i)" + "\n" + "}"
+    val code2 = "{ foo(i) }"
     succOf("foo(i)") shouldBe expected((code2, AlwaysEdge))
     succOf(code2) shouldBe expected(("_result_0", 1, AlwaysEdge))
   }