Repository: joernio/joern (scala)

Issue:
jssrc2cpg: member initialization missing
Similar to https://github.com/joernio/joern/issues/1757 (javasrc), jssrc2cpg also does not represent member initializations in the following code:

```
class Foo {
   x = "foo"
}
```

My suggestion would be to model it in the exact same way as it is done by the Java frontend. That way, the ossdataflowengine will pick this up correctly.

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

Patch:
diff --git a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
index 607ffa71d48f..c9cfbfd85731 100644
--- a/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
+++ b/joern-cli/frontends/c2cpg/src/main/scala/io/joern/c2cpg/astcreation/AstCreatorHelper.scala
@@ -411,8 +411,8 @@ trait AstCreatorHelper { this: AstCreator =>
     val code = childrenAsts.flatMap(_.nodes.headOption.map(_.asInstanceOf[NewCall].code)).mkString(",")
     val fakeStaticInitMethod =
       NewMethod()
-        .name("<sinit>")
-        .fullName(s"$name:<sinit>")
+        .name("<clinit>")
+        .fullName(s"$name:<clinit>")
         .code(code)
         .filename(filename)
         .lineNumber(lineNumber)
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 bbf450222186..362fc2cd2436 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
@@ -14,6 +14,7 @@ import io.shiftleft.codepropertygraph.generated.nodes.NewNamespaceBlock
 import io.shiftleft.codepropertygraph.generated.nodes.NewCall
 import io.shiftleft.codepropertygraph.generated.DispatchTypes
 import io.shiftleft.codepropertygraph.generated.Operators
+import io.shiftleft.codepropertygraph.generated.nodes.NewTypeDecl
 import ujson.Value
 
 import scala.util.Try
@@ -142,7 +143,48 @@ trait AstForTypesCreator { this: AstCreator =>
     } else {
       Seq(Ast(memberNode))
     }
+  }
 
+  private def astForClassMember(classElement: Value, typeDeclNode: NewTypeDecl, metaTypeDeclNode: NewTypeDecl): Ast = {
+    val nodeInfo = createBabelNodeInfo(classElement)
+    val memberNode = nodeInfo.node match {
+      case ClassMethod | ClassPrivateMethod =>
+        val function = createMethodAstAndNode(nodeInfo).methodNode
+        addModifier(function, nodeInfo.json)
+        val dynamicTypeHintFullName = Some(function.fullName)
+        createMemberNode(function.name, nodeInfo.code, dynamicTypeHintFullName)
+      case _ =>
+        val name = nodeInfo.node match {
+          case ClassProperty        => code(nodeInfo.json("key"))
+          case ClassPrivateProperty => code(nodeInfo.json("key")("id"))
+          // TODO: name field most likely needs adjustment for other Babel AST types
+          case _ => nodeInfo.code
+        }
+        createMemberNode(name, nodeInfo.code, dynamicTypeOption = None)
+    }
+    addModifier(memberNode, classElement)
+    if (classElement("static").bool) {
+      diffGraph.addEdge(metaTypeDeclNode, memberNode, EdgeTypes.AST)
+    } else {
+      diffGraph.addEdge(typeDeclNode, memberNode, EdgeTypes.AST)
+    }
+
+    if (hasKey(nodeInfo.json, "value") && !nodeInfo.json("value").isNull) {
+      val lhsAst = astForNode(nodeInfo.json("key"))
+      val rhsAst = astForNodeWithFunctionReference(nodeInfo.json("value"))
+      val callNode =
+        createCallNode(
+          nodeInfo.code,
+          Operators.assignment,
+          DispatchTypes.STATIC_DISPATCH,
+          nodeInfo.lineNumber,
+          nodeInfo.columnNumber
+        )
+      val argAsts = List(lhsAst, rhsAst)
+      createCallAst(callNode, argAsts)
+    } else {
+      Ast()
+    }
   }
 
   protected def astForEnum(tsEnum: BabelNodeInfo): Ast = {
@@ -238,32 +280,8 @@ trait AstForTypesCreator { this: AstCreator =>
     diffGraph.addEdge(metaTypeDeclNode, constructorBindingNode, EdgeTypes.BINDS)
     diffGraph.addEdge(constructorBindingNode, constructorNode, EdgeTypes.REF)
 
-    val classBodyElements = classMembers(clazz, withConstructor = false)
-
-    classBodyElements.foreach { classElement =>
-      val nodeInfo = createBabelNodeInfo(classElement)
-      val memberNode = nodeInfo.node match {
-        case ClassMethod | ClassPrivateMethod =>
-          val function = createMethodAstAndNode(nodeInfo).methodNode
-          addModifier(function, nodeInfo.json)
-          val dynamicTypeHintFullName = Some(function.fullName)
-          createMemberNode(function.name, nodeInfo.code, dynamicTypeHintFullName)
-        case _ =>
-          val name = nodeInfo.node match {
-            case ClassProperty        => code(nodeInfo.json("key"))
-            case ClassPrivateProperty => code(nodeInfo.json("key")("id"))
-            // TODO: name field most likely needs adjustment for other Babel AST types
-            case _ => nodeInfo.code
-          }
-          createMemberNode(name, nodeInfo.code, dynamicTypeOption = None)
-      }
-      addModifier(memberNode, classElement)
-      if (classElement("static").bool) {
-        diffGraph.addEdge(metaTypeDeclNode, memberNode, EdgeTypes.AST)
-      } else {
-        diffGraph.addEdge(typeDeclNode, memberNode, EdgeTypes.AST)
-      }
-    }
+    val memberInitCalls =
+      classMembers(clazz, withConstructor = false).map(m => astForClassMember(m, typeDeclNode, metaTypeDeclNode))
 
     methodAstParentStack.pop()
     dynamicInstanceTypeStack.pop()
@@ -271,6 +289,12 @@ trait AstForTypesCreator { this: AstCreator =>
     scope.popScope()
     scope.popScope()
 
+    if (memberInitCalls.nonEmpty) {
+      val init =
+        createAstForFakeStaticInitMethod(typeFullName, parserResult.filename, clazz.lineNumber, memberInitCalls)
+      Ast.storeInDiffGraph(init, diffGraph)
+      diffGraph.addEdge(typeDeclNode, init.nodes.head, EdgeTypes.AST)
+    }
     Ast(metaTypeRefNode)
   }
 
diff --git a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala
index b8a5ba2f54c6..64c77f680857 100644
--- a/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/main/scala/io/joern/jssrc2cpg/astcreation/AstNodeBuilder.scala
@@ -341,8 +341,8 @@ trait AstNodeBuilder { this: AstCreator =>
     val code = childrenAsts.flatMap(_.nodes.headOption.map(_.asInstanceOf[NewCall].code)).mkString(",")
     val fakeStaticInitMethod =
       NewMethod()
-        .name("<sinit>")
-        .fullName(s"$name:<sinit>")
+        .name("<clinit>")
+        .fullName(s"$name:<clinit>")
         .code(code)
         .filename(filename)
         .lineNumber(lineNumber)


Test patch:
diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/EnumTypeTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/EnumTypeTests.scala
index c5467250efc7..6174f4ec2a02 100644
--- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/EnumTypeTests.scala
+++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/EnumTypeTests.scala
@@ -31,11 +31,11 @@ class EnumTypeTests extends CCodeToCpgSuite(fileSuffix = FileDefaults.CPP_EXT) {
           green.name shouldBe "green"
           blue.name shouldBe "blue"
         }
-        inside(color.astChildren.isMethod.l) { case List(sinit) =>
-          sinit.name shouldBe "<sinit>"
-          sinit.fullName shouldBe "color:<sinit>"
-          sinit.code shouldBe "green = 20"
-          inside(sinit.ast.isCall.l) { case List(greenInit) =>
+        inside(color.astChildren.isMethod.l) { case List(clinit) =>
+          clinit.name shouldBe "<clinit>"
+          clinit.fullName shouldBe "color:<clinit>"
+          clinit.code shouldBe "green = 20"
+          inside(clinit.ast.isCall.l) { case List(greenInit) =>
             greenInit.code shouldBe "green = 20"
           }
         }
@@ -64,11 +64,11 @@ class EnumTypeTests extends CCodeToCpgSuite(fileSuffix = FileDefaults.CPP_EXT) {
             green.name shouldBe "green"
             blue.name shouldBe "blue"
           }
-          inside(color.astChildren.isMethod.l) { case List(sinit) =>
-            sinit.name shouldBe "<sinit>"
-            sinit.fullName shouldBe "color:<sinit>"
-            sinit.code shouldBe "green = 20"
-            inside(sinit.ast.isCall.l) { case List(greenInit) =>
+          inside(color.astChildren.isMethod.l) { case List(clinit) =>
+            clinit.name shouldBe "<clinit>"
+            clinit.fullName shouldBe "color:<clinit>"
+            clinit.code shouldBe "green = 20"
+            inside(clinit.ast.isCall.l) { case List(greenInit) =>
               greenInit.code shouldBe "green = 20"
             }
           }
@@ -91,11 +91,11 @@ class EnumTypeTests extends CCodeToCpgSuite(fileSuffix = FileDefaults.CPP_EXT) {
           low.name shouldBe "low"
           low.typeFullName shouldBe "char"
         }
-        inside(altitude.astChildren.isMethod.l) { case List(sinit) =>
-          sinit.name shouldBe "<sinit>"
-          sinit.fullName shouldBe "altitude:<sinit>"
-          sinit.code shouldBe "high='h',low='l'"
-          inside(sinit.ast.isCall.code.l) { case List(highInit, lowInit) =>
+        inside(altitude.astChildren.isMethod.l) { case List(clinit) =>
+          clinit.name shouldBe "<clinit>"
+          clinit.fullName shouldBe "altitude:<clinit>"
+          clinit.code shouldBe "high='h',low='l'"
+          inside(clinit.ast.isCall.code.l) { case List(highInit, lowInit) =>
             highInit shouldBe "high='h'"
             lowInit shouldBe "low='l'"
           }
diff --git a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/StructTypeTests.scala b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/StructTypeTests.scala
index dd898b61fd82..1688636d1b43 100644
--- a/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/StructTypeTests.scala
+++ b/joern-cli/frontends/c2cpg/src/test/scala/io/joern/c2cpg/passes/types/StructTypeTests.scala
@@ -23,8 +23,8 @@ class StructTypeTests extends CCodeToCpgSuite {
     }
 
     "initialize array members correctly" in {
-      val List(fakeInitMethod)                  = cpg.method.nameExact("<sinit>").l
-      val List(aInitCall, bInitCall, cInitCall) = fakeInitMethod.call.nameExact(Operators.arrayInitializer).l
+      val List(clInitMethod)                    = cpg.method.nameExact("<clinit>").l
+      val List(aInitCall, bInitCall, cInitCall) = clInitMethod.call.nameExact(Operators.arrayInitializer).l
 
       aInitCall.code shouldBe "a[SIZE]"
       val List(argAInit) = aInitCall.argument.l
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 9eea72366391..4d15399dbb39 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
@@ -59,6 +59,28 @@ class JsClassesAstCreationPassTest extends AbstractPassTest {
       memberFoo.code shouldBe "foo() {}"
     }
 
+    "have member with initialization in TYPE_DECL for ClassA" in AstFixture("""
+        |class ClassA {
+        |  a = 1
+        |  b = "foo"
+        |  c = true
+        |}""".stripMargin) { cpg =>
+      val List(classATypeDecl) = cpg.typeDecl.nameExact("ClassA").fullNameExact("code.js::program:ClassA").l
+      val List(a, b, c)        = classATypeDecl.member.l
+      a.name shouldBe "a"
+      a.code shouldBe "a = 1"
+      b.name shouldBe "b"
+      b.code shouldBe """b = "foo""""
+      c.name shouldBe "c"
+      c.code shouldBe "c = true"
+
+      val List(clInitMethod)                    = cpg.typeDecl.nameExact("ClassA").method.nameExact("<clinit>").l
+      val List(aInitCall, bInitCall, cInitCall) = clInitMethod.ast.isCall.l
+      aInitCall.code shouldBe "a = 1"
+      bInitCall.code shouldBe """b = "foo""""
+      cInitCall.code shouldBe "c = true"
+    }
+
     "have method for non-static method in ClassA AST" in AstFixture("""
         |var x = class ClassA {
         |  foo() {}
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala
index 2104bdf6b7c5..2e929edb1d99 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/ast/TsAstCreationPassTest.scala
@@ -94,7 +94,7 @@ class TsAstCreationPassTest extends AbstractPassTest {
         direction.fullName shouldBe "code.ts::program:Direction"
         direction.filename shouldBe "code.ts"
         direction.file.name.head shouldBe "code.ts"
-        inside(direction.method.name("<sinit>").l) { case List(init) =>
+        inside(direction.method.name("<clinit>").l) { case List(init) =>
           init.block.astChildren.isCall.code.head shouldBe "Up = 1"
         }
         inside(cpg.typeDecl("Direction").member.l) { case List(up, down, left, right) =>