Repository: joernio/joern (scala)

Issue:
jssrc: missing edge between local and identifier for import <name> = ...
```
joern> importCode.jssrc.fromString("""import path = require('path')""") 
res0: Cpg = Cpg (Graph [51 nodes])
                                               
joern> cpg.local.l 
res1: List[Local] = List(
  Local(               
    id -> 8L,            
    closureBindingId -> None,
    code -> "path",                 
    columnNumber -> None,                                                                      
    dynamicTypeHintFullName -> ArraySeq(),
    lineNumber -> None,
    name -> "path",
    order -> 0,          
    typeFullName -> "ANY"
  )
)
joern> cpg.local.referencingIdentifiers.l 
res2: List[Identifier] = List()

joern> cpg.identifier.l
res3: List[Identifier] = List(
  Identifier(
    id -> 12L,
    argumentIndex -> 1,
    argumentName -> None,
    code -> "path",
    columnNumber -> Some(value = 0),
    dynamicTypeHintFullName -> ArraySeq("<export>::path.js"),
    lineNumber -> Some(value = 1),
    name -> "path",
    order -> 1,
    typeFullName -> "ANY"
  )
)

joern> cpg.identifier.refsTo.l 
res4: List[Declaration] = List()
```

Also, the `require(...)` in there is a static call, which is not the case for `let path = require('path')`. I believe some linker passes expect an identifier `require` as the receiver, so best adjust that as well.

Pull request description:
Fixes: https://github.com/joernio/joern/issues/2066
Fixes: https://github.com/joernio/joern/issues/2067
Fixes: https://github.com/joernio/joern/issues/2069

Patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForDeclarationsCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForDeclarationsCreator.scala
index 17a24d26b728..57dd77a920eb 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForDeclarationsCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForDeclarationsCreator.scala
@@ -106,6 +106,7 @@ trait AstForDeclarationsCreator { this: AstCreator =>
       val id           = createIdentifierNode(s"_$strippedCode", declaration)
       val localNode    = createLocalNode(id.code, Defines.ANY)
       scope.addVariable(id.code, localNode, BlockScope)
+      scope.addVariableReference(id.code, id)
       diffGraph.addEdge(localAstParentStack.head, localNode, EdgeTypes.AST)
 
       val sourceCallArgNode =
@@ -338,6 +339,7 @@ trait AstForDeclarationsCreator { this: AstCreator =>
     val destNode  = createIdentifierNode(destName, nodeInfo)
     val localNode = createLocalNode(destName, Defines.ANY)
     scope.addVariable(destName, localNode, BlockScope)
+    scope.addVariableReference(destName, destNode)
     diffGraph.addEdge(localAstParentStack.head, localNode, EdgeTypes.AST)
 
     val destAst           = Ast(destNode)
@@ -345,13 +347,21 @@ trait AstForDeclarationsCreator { this: AstCreator =>
     val sourceCall = createCallNode(
       s"$REQUIRE_KEYWORD(${sourceCallArgNode.code})",
       REQUIRE_KEYWORD,
-      DispatchTypes.STATIC_DISPATCH,
+      DispatchTypes.DYNAMIC_DISPATCH,
       nodeInfo.lineNumber,
       nodeInfo.columnNumber
     )
 
+    val receiverNode = createIdentifierNode(REQUIRE_KEYWORD, nodeInfo)
+    val thisNode     = createIdentifierNode("this", nodeInfo)
+    scope.addVariableReference(thisNode.name, thisNode)
+    val callAst = createCallAst(
+      sourceCall,
+      List(Ast(sourceCallArgNode)),
+      receiver = Some(Ast(receiverNode)),
+      base = Some(Ast(thisNode))
+    )
     val sourceAst = if (isImportN) {
-      val callAst = createCallAst(sourceCall, List(Ast(sourceCallArgNode)))
       val fieldAccessCall = createFieldAccessCallAst(
         callAst,
         createFieldIdentifierNode(name, nodeInfo.lineNumber, nodeInfo.columnNumber),
@@ -360,7 +370,6 @@ trait AstForDeclarationsCreator { this: AstCreator =>
       )
       fieldAccessCall
     } else {
-      val callAst = createCallAst(sourceCall, List(Ast(sourceCallArgNode)))
       callAst
     }
     val assigmentCallAst =
@@ -577,9 +586,10 @@ trait AstForDeclarationsCreator { this: AstCreator =>
     localAstParentStack.push(blockNode)
 
     val localNode = createLocalNode(localTmpName, Defines.ANY)
+    val tmpNode   = createIdentifierNode(localTmpName, pattern)
     diffGraph.addEdge(localAstParentStack.head, localNode, EdgeTypes.AST)
-
-    val tmpNode = createIdentifierNode(localTmpName, pattern)
+    scope.addVariable(localTmpName, localNode, BlockScope)
+    scope.addVariableReference(localTmpName, tmpNode)
 
     val rhsAssignmentAst = paramName.map(createParamAst(pattern, _, sourceAst)).getOrElse(sourceAst)
     val assignmentTmpCallAst =
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala
index e58eb1e28b6c..1b0ca842bb19 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForExpressionsCreator.scala
@@ -1,5 +1,6 @@
 package io.joern.jssrc2cpg.astcreation
 
+import io.joern.jssrc2cpg.datastructures.BlockScope
 import io.joern.jssrc2cpg.parser.BabelAst._
 import io.joern.jssrc2cpg.parser.BabelNodeInfo
 import io.joern.jssrc2cpg.passes.{Defines, EcmaBuiltins, GlobalBuiltins}
@@ -80,7 +81,7 @@ trait AstForExpressionsCreator { this: AstCreator =>
           val receiverAst = astForNodeWithFunctionReference(callee.json)
           val thisNode    = createIdentifierNode("this", callee)
           scope.addVariableReference(thisNode.name, thisNode)
-          (receiverAst, thisNode, callee.code)
+          (receiverAst, thisNode, calleeCode)
       }
       handleCallNodeArgs(callExpr, receiverAst, baseNode, callName)
     }
@@ -106,9 +107,9 @@ trait AstForExpressionsCreator { this: AstCreator =>
 
     val tmpAllocName      = generateUnusedVariableName(usedVariableNames, "_tmp")
     val localTmpAllocNode = createLocalNode(tmpAllocName, Defines.ANY)
+    val tmpAllocNode1     = createIdentifierNode(tmpAllocName, newExpr)
     diffGraph.addEdge(localAstParentStack.head, localTmpAllocNode, EdgeTypes.AST)
-
-    val tmpAllocNode1 = createIdentifierNode(tmpAllocName, newExpr)
+    scope.addVariableReference(tmpAllocName, tmpAllocNode1)
 
     val allocCallNode =
       createCallNode(".alloc", Operators.alloc, DispatchTypes.STATIC_DISPATCH, newExpr.lineNumber, newExpr.columnNumber)
@@ -349,9 +350,9 @@ trait AstForExpressionsCreator { this: AstCreator =>
 
       val tmpName      = generateUnusedVariableName(usedVariableNames, "_tmp")
       val localTmpNode = createLocalNode(tmpName, Defines.ANY)
-      diffGraph.addEdge(localAstParentStack.head, localTmpNode, EdgeTypes.AST)
-
       val tmpArrayNode = createIdentifierNode(tmpName, arrExpr)
+      diffGraph.addEdge(localAstParentStack.head, localTmpNode, EdgeTypes.AST)
+      scope.addVariableReference(tmpName, tmpArrayNode)
 
       val arrayCallNode = createCallNode(
         EcmaBuiltins.arrayFactory + "()",
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForFunctionsCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForFunctionsCreator.scala
index 4dae0814204b..1d7d44268377 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForFunctionsCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForFunctionsCreator.scala
@@ -353,7 +353,7 @@ trait AstForFunctionsCreator { this: AstCreator =>
             createBlockStatementAsts(bodyJson("body"))
           case _ =>
             // when body is just one expression like const foo = () => 42, generate a Return node
-            createReturnAst(createReturnNode(bodyNodeInfo), List(astForNode(bodyJson))) :: Nil
+            createReturnAst(createReturnNode(bodyNodeInfo), List(astForNodeWithFunctionReference(bodyJson))) :: Nil
         }
       case _ => createBlockStatementAsts(bodyJson("body"))
     }
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 7f3156732a26..da40029332de 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
@@ -295,9 +295,9 @@ trait AstForStatementsCreator { this: AstCreator =>
     // _iterator assignment:
     val iteratorName      = generateUnusedVariableName(usedVariableNames, "_iterator")
     val iteratorLocalNode = createLocalNode(iteratorName, Defines.ANY)
+    val iteratorNode      = createIdentifierNode(iteratorName, forInOfStmt)
     diffGraph.addEdge(localAstParentStack.head, iteratorLocalNode, EdgeTypes.AST)
-
-    val iteratorNode = createIdentifierNode(iteratorName, forInOfStmt)
+    scope.addVariableReference(iteratorName, iteratorNode)
 
     val callNode = createCallNode(
       "Object.keys(" + collectionName + ")[Symbol.iterator]()",
@@ -356,8 +356,9 @@ trait AstForStatementsCreator { this: AstCreator =>
     // _result:
     val resultName      = generateUnusedVariableName(usedVariableNames, "_result")
     val resultLocalNode = createLocalNode(resultName, Defines.ANY)
+    val resultNode      = createIdentifierNode(resultName, forInOfStmt)
     diffGraph.addEdge(localAstParentStack.head, resultLocalNode, EdgeTypes.AST)
-    val resultNode = createIdentifierNode(resultName, forInOfStmt)
+    scope.addVariableReference(resultName, resultNode)
 
     // loop variable:
     val nodeInfo = createBabelNodeInfo(forInOfStmt.json("left"))
@@ -367,8 +368,9 @@ trait AstForStatementsCreator { this: AstCreator =>
     }
 
     val loopVariableLocalNode = createLocalNode(loopVariableName, Defines.ANY)
+    val loopVariableNode      = createIdentifierNode(loopVariableName, forInOfStmt)
     diffGraph.addEdge(localAstParentStack.head, loopVariableLocalNode, EdgeTypes.AST)
-    val loopVariableNode = createIdentifierNode(loopVariableName, forInOfStmt)
+    scope.addVariableReference(loopVariableName, loopVariableNode)
 
     // while loop:
     val whileLoopNode =


Test patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/DependencyAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/DependencyAstCreationPassTest.scala
index e28670f66481..d08b69ab411f 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/DependencyAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/DependencyAstCreationPassTest.scala
@@ -66,6 +66,17 @@ class DependencyAstCreationPassTest extends AbstractPassTest {
       depB.dependencyGroupId shouldBe Some("depB")
     }
 
+    "have correct locals and require calls for imports" in AstFixture("import path = require('path')") { cpg =>
+      val List(localPath) = cpg.local.l
+      localPath.name shouldBe "path"
+      localPath.referencingIdentifiers.head.name shouldBe "path"
+
+      val List(reqCall) = cpg.call.codeExact("require(\"path\")").l
+      reqCall.dispatchType shouldBe DispatchTypes.DYNAMIC_DISPATCH
+      reqCall.receiver.head.code shouldBe "require"
+      reqCall.argument.argumentIndex(1).head.code shouldBe "\"path\""
+    }
+
     "have correct import nodes" in AstFixture("""
         |import {a} from "depA";
         |import {b} from "depB";
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/MixedAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/MixedAstCreationPassTest.scala
index e493cf32a8f2..6c4f438ebd6b 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/MixedAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/MixedAstCreationPassTest.scala
@@ -532,14 +532,18 @@ class MixedAstCreationPassTest extends AbstractPassTest {
     "have correct structure for object destruction assignment with declaration" in AstFixture("var {a, b} = x") { cpg =>
       val List(program)      = cpg.method.nameExact(":program").l
       val List(programBlock) = program.astChildren.isBlock.l
-      programBlock.astChildren.isLocal.nameExact("a").size shouldBe 1
-      programBlock.astChildren.isLocal.nameExact("b").size shouldBe 1
+      val List(localA)       = programBlock.astChildren.isLocal.nameExact("a").l
+      localA.referencingIdentifiers.name.head shouldBe "a"
+      val List(localB) = programBlock.astChildren.isLocal.nameExact("b").l
+      localB.referencingIdentifiers.name.head shouldBe "b"
 
       val List(destructionBlock) = programBlock.astChildren.isBlock.l
       destructionBlock.code shouldBe "var {a, b} = x"
-      destructionBlock.astChildren.isLocal.nameExact("_tmp_0").size shouldBe 1
       destructionBlock.astChildren.isCall.codeExact("_tmp_0 = x").size shouldBe 1
 
+      val List(localTmp) = destructionBlock.astChildren.isLocal.nameExact("_tmp_0").l
+      localTmp.referencingIdentifiers.name.head shouldBe "_tmp_0"
+
       val List(assignmentToA) = destructionBlock.astChildren.isCall.codeExact("a = _tmp_0.a").l
       assignmentToA.astChildren.isIdentifier.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 e4accd95dd27..64b3ddd0543d 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
@@ -515,6 +515,18 @@ class SimpleAstCreationPassTest extends AbstractPassTest {
       lambda2MethodRef.methodFullName shouldBe lambda2FullName
     }
 
+    "be correct for lambdas returning lambdas" in AstFixture("() => async () => { }") { cpg =>
+      cpg.method.fullName.l shouldBe List(
+        "code.js::program",
+        "code.js::program:anonymous:anonymous",
+        "code.js::program:anonymous"
+      )
+      val List(ret) = cpg.method.fullNameExact("code.js::program:anonymous").block.astChildren.isReturn.l
+      ret.code shouldBe "async () => { }"
+      val List(ref: MethodRef) = ret.astChildren.l
+      ref.methodFullName shouldBe "code.js::program:anonymous:anonymous"
+    }
+
     "be correct for ThisExpression" in AstFixture("function foo() { this.bar = 1 }") { cpg =>
       val List(thisIdentifier: Identifier) = cpg.fieldAccess.argument(1).l
       thisIdentifier.name shouldBe "this"
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/VueJsDomAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/VueJsDomAstCreationPassTest.scala
index 41b3eb6a82dd..de40f34afb30 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/VueJsDomAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/VueJsDomAstCreationPassTest.scala
@@ -149,29 +149,30 @@ class VueJsDomAstCreationPassTest extends AbstractDomPassTest {
       )
       cpg.local.code.l shouldBe List("Component", "Prop", "Vue", "HelloWorld", "msg")
 
-      inside(cpg.identifier.l) { case List(comp, prop, vue, msg, helloWorld1, exports, helloWorld2) =>
-        comp.name shouldBe "Component"
-        comp.code shouldBe "Component"
-        prop.name shouldBe "Prop"
-        prop.code shouldBe "Prop"
-        vue.name shouldBe "Vue"
-        vue.code shouldBe "Vue"
+      inside(cpg.identifier.nameNot("this", "require").l) {
+        case List(comp, prop, vue, msg, helloWorld1, exports, helloWorld2) =>
+          comp.name shouldBe "Component"
+          comp.code shouldBe "Component"
+          prop.name shouldBe "Prop"
+          prop.code shouldBe "Prop"
+          vue.name shouldBe "Vue"
+          vue.code shouldBe "Vue"
 
-        exports.name shouldBe "exports"
-        exports.code shouldBe "exports"
-        msg.name shouldBe "msg"
-        msg.code shouldBe "msg"
-        parentTemplateDom(msg).name shouldBe "JSXExpressionContainer"
-        parentTemplateDom(msg).code shouldBe "{{ msg }}"
-        parentTemplateDom(parentTemplateDom(msg)).name shouldBe "JSXElement"
-        parentTemplateDom(parentTemplateDom(msg)).code shouldBe "<h1>{{ msg }}</h1>"
+          exports.name shouldBe "exports"
+          exports.code shouldBe "exports"
+          msg.name shouldBe "msg"
+          msg.code shouldBe "msg"
+          parentTemplateDom(msg).name shouldBe "JSXExpressionContainer"
+          parentTemplateDom(msg).code shouldBe "{{ msg }}"
+          parentTemplateDom(parentTemplateDom(msg)).name shouldBe "JSXElement"
+          parentTemplateDom(parentTemplateDom(msg)).code shouldBe "<h1>{{ msg }}</h1>"
 
-        // from implicit identifier for the class definition
-        helloWorld1.name shouldBe "HelloWorld"
-        helloWorld1.code shouldBe "HelloWorld"
-        // from the export
-        helloWorld2.name shouldBe "HelloWorld"
-        helloWorld2.code shouldBe "HelloWorld"
+          // from implicit identifier for the class definition
+          helloWorld1.name shouldBe "HelloWorld"
+          helloWorld1.code shouldBe "HelloWorld"
+          // from the export
+          helloWorld2.name shouldBe "HelloWorld"
+          helloWorld2.code shouldBe "HelloWorld"
       }
 
       inside(cpg.imports.l) { case List(component, prop, vue) =>