Repository: joernio/joern (scala)

Issue:
jssrc: missing locals / closure bindings for implict variables from class definitions
it seems that #2046 wasn't quite enough, compare:


```
joern> importCode.jssrc.fromString("""class A{} function b() { new A(); } """) 
joern> cpg.method.name(":program").local.name("A").l 
res27: List[Local] = List()
joern> cpg.method.name("b").local.name("A").l 
res21: List[Local] = List(
  Local(
    id -> 43L,
    closureBindingId -> None, // MISSING
    code -> "A",
    columnNumber -> None,
    dynamicTypeHintFullName -> ArraySeq(),
    lineNumber -> None,
    name -> "A",
    order -> 0,
    typeFullName -> "ANY"
  )
)

joern> importCode.jssrc.fromString("""let A = class A{} ; function b() { new A(); } """) 
joern> cpg.method.name(":program").local.name("A").l 
res29: List[Local] = List(
  Local(
    id -> 22L,
    closureBindingId -> None,
    code -> "A",
    columnNumber -> None,
    dynamicTypeHintFullName -> ArraySeq(),
    lineNumber -> None,
    name -> "A",
    order -> 0,
    typeFullName -> "ANY"
  )
)
joern> cpg.method.name("b").local.name("A").l 
res23: List[Local] = List(
  Local(
    id -> 43L,
    closureBindingId -> Some(value = "tmp.js::program:b:A"),
    code -> "A",
    columnNumber -> None,
    dynamicTypeHintFullName -> ArraySeq(),
    lineNumber -> None,
    name -> "A",
    order -> 0,
    typeFullName -> "ANY"
  )
)
```

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

Patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala
index 61e93f5b6efa..1cb9156fdf5e 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstForTypesCreator.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.x2cpg.datastructures.Stack._
@@ -334,6 +335,12 @@ trait AstForTypesCreator { this: AstCreator =>
       val classIdNode =
         createIdentifierNode(typeName, Some(constructorNode.fullName), clazz.lineNumber, clazz.columnNumber)
       val constructorRefNode = createMethodRefNode(constructorNode.code, constructorNode.fullName, clazz)
+
+      val idLocal = createLocalNode(typeName, Defines.ANY)
+      diffGraph.addEdge(localAstParentStack.head, idLocal, EdgeTypes.AST)
+      scope.addVariable(typeName, idLocal, BlockScope)
+      scope.addVariableReference(typeName, classIdNode)
+
       createAssignmentCallAst(
         classIdNode,
         constructorRefNode,


Test patch:
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/JsClassesAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/JsClassesAstCreationPassTest.scala
index ff8c64192e2d..a65be1560ceb 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/JsClassesAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/JsClassesAstCreationPassTest.scala
@@ -48,6 +48,19 @@ class JsClassesAstCreationPassTest extends AbstractPassTest {
       }
     }
 
+    "have locals / closure bindings for implicit variables from class definitions" in AstFixture("""
+        |class A {}
+        |function b() {
+        |  new A();
+        |}""".stripMargin) { cpg =>
+      cpg.typeDecl.nameExact("A").fullNameExact("code.js::program:A").size shouldBe 1
+      val List(localA) = cpg.method.name(":program").local.name("A").l
+      localA.code shouldBe "A"
+      val List(funcLocalA) = cpg.method.name("b").local.name("A").l
+      funcLocalA.code shouldBe "A"
+      funcLocalA.closureBindingId shouldBe Some("code.js::program:b:A")
+    }
+
     "have constructor binding in TYPE_DECL for ClassA" in AstFixture("""
         |var x = class ClassA {
         |  constructor() {}
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 0f21cf15b369..41b3eb6a82dd 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
@@ -147,7 +147,7 @@ class VueJsDomAstCreationPassTest extends AbstractDomPassTest {
         "HelloWorld = test.vue::program:HelloWorld:<init>",
         "exports[\"default\"] = HelloWorld"
       )
-      cpg.local.code.l shouldBe List("Component", "Prop", "Vue", "msg")
+      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"