Repository: joernio/joern (scala)

Issue:
jssrc: incomplete code field for destructuring assignments
```
joern> importCode.jssrc.fromString("""const { logger } = require("../Logger");""")
val res6: io.shiftleft.codepropertygraph.Cpg = Cpg (Graph [65 nodes])
                                                                                                                                                                                               
joern> cpg.method.body.expressionDown.code.p
val res7: List[String] = ::(head -> "{ logger }", next -> List())
```

The block node for this destructuring assignment only has the code of left-hand-side. This is a purely presentational matter - I don't know think that it breaks anything, but it would be nice to have a more complete code field there.

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

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 e1487011c671..17a24d26b728 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
@@ -265,8 +265,9 @@ trait AstForDeclarationsCreator { this: AstCreator =>
   }
 
   private def astForVariableDeclarator(declarator: Value, scopeType: ScopeType, kind: String): Ast = {
-    val id   = createBabelNodeInfo(declarator("id"))
-    val init = Try(createBabelNodeInfo(declarator("init"))).toOption
+    val id             = createBabelNodeInfo(declarator("id"))
+    val init           = Try(createBabelNodeInfo(declarator("init"))).toOption
+    val declaratorCode = s"$kind ${code(declarator)}"
 
     val typeFullName = init match {
       case Some(f @ BabelNodeInfo(_: FunctionLike, _, _, _, _, _, _)) =>
@@ -292,7 +293,7 @@ trait AstForDeclarationsCreator { this: AstCreator =>
       val nodeInfo = createBabelNodeInfo(id.json)
       nodeInfo.node match {
         case ObjectPattern | ArrayPattern =>
-          astForDeconstruction(nodeInfo, sourceAst)
+          astForDeconstruction(nodeInfo, sourceAst, declaratorCode)
         case _ =>
           val destAst = id.node match {
             case Identifier => astForIdentifier(id, Some(typeFullName))
@@ -303,7 +304,7 @@ trait AstForDeclarationsCreator { this: AstCreator =>
             createAssignmentCallAst(
               destAst,
               sourceAst,
-              s"$kind ${code(declarator)}",
+              declaratorCode,
               line = line(declarator),
               column = column(declarator)
             )
@@ -470,7 +471,7 @@ trait AstForDeclarationsCreator { this: AstCreator =>
     val lhsAst = nodeInfo.node match {
       case ObjectPattern | ArrayPattern =>
         val sourceAst = astForNodeWithFunctionReference(createBabelNodeInfo(rhsElement).json)
-        astForDeconstruction(nodeInfo, sourceAst)
+        astForDeconstruction(nodeInfo, sourceAst, element.code)
       case _ => astForNode(lhsElement)
     }
 
@@ -513,7 +514,7 @@ trait AstForDeclarationsCreator { this: AstCreator =>
     val lhsAst = nodeInfo.node match {
       case ObjectPattern | ArrayPattern =>
         val sourceAst = astForNodeWithFunctionReference(createBabelNodeInfo(rhsElement).json)
-        astForDeconstruction(nodeInfo, sourceAst)
+        astForDeconstruction(nodeInfo, sourceAst, element.code)
       case _ => astForNode(lhsElement)
     }
 
@@ -563,10 +564,15 @@ trait AstForDeclarationsCreator { this: AstCreator =>
     createTernaryCallAst(testAst, sourceAst, Ast(falseNode), pattern.lineNumber, pattern.columnNumber)
   }
 
-  protected def astForDeconstruction(pattern: BabelNodeInfo, sourceAst: Ast, paramName: Option[String] = None): Ast = {
+  protected def astForDeconstruction(
+    pattern: BabelNodeInfo,
+    sourceAst: Ast,
+    code: String,
+    paramName: Option[String] = None
+  ): Ast = {
     val localTmpName = generateUnusedVariableName(usedVariableNames, "_tmp")
 
-    val blockNode = createBlockNode(pattern)
+    val blockNode = createBlockNode(pattern, Some(code))
     scope.pushNewBlockScope(blockNode)
     localAstParentStack.push(blockNode)
 
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 89a7a0720b71..85ca660aa513 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
@@ -179,7 +179,7 @@ trait AstForExpressionsCreator { this: AstCreator =>
     nodeInfo.node match {
       case ObjectPattern | ArrayPattern =>
         val rhsAst = astForNodeWithFunctionReference(assignment.json("right"))
-        astForDeconstruction(nodeInfo, rhsAst)
+        astForDeconstruction(nodeInfo, rhsAst, assignment.code)
       case _ =>
         val lhsAst = astForNode(assignment.json("left"))
         val rhsAst = astForNodeWithFunctionReference(assignment.json("right"))
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 ff52115f0c32..4dae0814204b 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
@@ -90,7 +90,7 @@ trait AstForFunctionsCreator { this: AstCreator =>
               nodeInfo.columnNumber
             )
             val rhsAst = astForNodeWithFunctionReference(rhsElement)
-            additionalBlockStatements.addOne(astForDeconstruction(lhsNodeInfo, rhsAst, Some(paramName)))
+            additionalBlockStatements.addOne(astForDeconstruction(lhsNodeInfo, rhsAst, nodeInfo.code, Some(paramName)))
             param
           case _ =>
             additionalBlockStatements.addOne(convertParamWithDefault(nodeInfo))
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 7c9eb40f24a9..c4c23204e9f6 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
@@ -450,10 +450,10 @@ trait AstNodeBuilder { this: AstCreator =>
       .lineNumber(line)
       .columnNumber(column)
 
-  protected def createBlockNode(node: BabelNodeInfo): NewBlock =
+  protected def createBlockNode(node: BabelNodeInfo, customCode: Option[String] = None): NewBlock =
     NewBlock()
       .typeFullName(Defines.ANY)
-      .code(node.code)
+      .code(customCode.getOrElse(node.code))
       .lineNumber(node.lineNumber)
       .columnNumber(node.columnNumber)
 


Test patch:
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 c31d371abb19..e493cf32a8f2 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
@@ -536,11 +536,11 @@ class MixedAstCreationPassTest extends AbstractPassTest {
       programBlock.astChildren.isLocal.nameExact("b").size shouldBe 1
 
       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(assignmentToA) =
-        destructionBlock.astChildren.isCall.codeExact("a = _tmp_0.a").l
+      val List(assignmentToA) = destructionBlock.astChildren.isCall.codeExact("a = _tmp_0.a").l
       assignmentToA.astChildren.isIdentifier.size shouldBe 1
 
       val List(fieldAccessA) = assignmentToA.astChildren.isCall.codeExact("_tmp_0.a").l
diff --git a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/MixedCfgCreationPassTest.scala b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/MixedCfgCreationPassTest.scala
index 03fe07cc6c18..8951ba8fc33e 100644
--- a/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/MixedCfgCreationPassTest.scala
+++ b/joern-cli/frontends/jssrc2cpg/src/test/scala/io/joern/jssrc2cpg/passes/cfg/MixedCfgCreationPassTest.scala
@@ -29,12 +29,12 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("b", 1) shouldBe expected(("_tmp_0.b", AlwaysEdge))
       succOf("_tmp_0.b") shouldBe expected(("b = _tmp_0.b", AlwaysEdge))
       succOf("b = _tmp_0.b") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
-      succOf("_tmp_0", 3) shouldBe expected(("{a, b}", AlwaysEdge))
-      succOf("{a, b}") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("var {a, b} = x", AlwaysEdge))
+      succOf("var {a, b} = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for object destruction assignment with declaration and ternary init" in {
-      implicit val cpg: Cpg = code("const { a, b } = test() ? foo() : bar();")
+      implicit val cpg: Cpg = code("const { a, b } = test() ? foo() : bar()")
       succOf(":program") shouldBe expected(("_tmp_0", AlwaysEdge))
       succOf("_tmp_0") shouldBe expected(("test", AlwaysEdge))
       succOf("test") shouldBe expected(("this", AlwaysEdge))
@@ -56,8 +56,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("b", 1) shouldBe expected(("_tmp_0.b", AlwaysEdge))
       succOf("_tmp_0.b") shouldBe expected(("b = _tmp_0.b", AlwaysEdge))
       succOf("b = _tmp_0.b") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
-      succOf("_tmp_0", 3) shouldBe expected(("{ a, b }", AlwaysEdge))
-      succOf("{ a, b }") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("const { a, b } = test() ? foo() : bar()", AlwaysEdge))
+      succOf("const { a, b } = test() ? foo() : bar()") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for object destruction assignment with reassignment" in {
@@ -78,8 +78,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("b") shouldBe expected(("_tmp_0.b", AlwaysEdge))
       succOf("_tmp_0.b") shouldBe expected(("m = _tmp_0.b", AlwaysEdge))
       succOf("m = _tmp_0.b") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
-      succOf("_tmp_0", 3) shouldBe expected(("{a: n, b: m}", AlwaysEdge))
-      succOf("{a: n, b: m}") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("var {a: n, b: m} = x", AlwaysEdge))
+      succOf("var {a: n, b: m} = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for object destruction assignment with reassignment and defaults" in {
@@ -124,8 +124,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
         expected(("m = _tmp_0.b === void 0 ? 2 : _tmp_0.b", AlwaysEdge))
       succOf("m = _tmp_0.b === void 0 ? 2 : _tmp_0.b") shouldBe
         expected(("_tmp_0", 5, AlwaysEdge))
-      succOf("_tmp_0", 5) shouldBe expected(("{a: n = 1, b: m = 2}", AlwaysEdge))
-      succOf("{a: n = 1, b: m = 2}") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 5) shouldBe expected(("var {a: n = 1, b: m = 2} = x", AlwaysEdge))
+      succOf("var {a: n = 1, b: m = 2} = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for object destruction assignment with rest" in {
@@ -147,8 +147,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("_tmp_0.rest") shouldBe expected(("rest = _tmp_0.rest", AlwaysEdge))
       succOf("rest = _tmp_0.rest") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
 
-      succOf("_tmp_0", 3) shouldBe expected(("{a, ...rest}", AlwaysEdge))
-      succOf("{a, ...rest}") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("var {a, ...rest} = x", AlwaysEdge))
+      succOf("var {a, ...rest} = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for object destruction assignment with computed property name" in {
@@ -164,8 +164,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("_tmp_0.propName") shouldBe expected(("n = _tmp_0.propName", AlwaysEdge))
 
       succOf("n = _tmp_0.propName") shouldBe expected(("_tmp_0", 2, AlwaysEdge))
-      succOf("_tmp_0", 2) shouldBe expected(("{[propName]: n}", AlwaysEdge))
-      succOf("{[propName]: n}") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 2) shouldBe expected(("var {[propName]: n} = x", AlwaysEdge))
+      succOf("var {[propName]: n} = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for nested object destruction assignment with defaults as parameter" in {
@@ -213,8 +213,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("b", 1) shouldBe expected(("_tmp_1.b", AlwaysEdge))
       succOf("_tmp_1.b") shouldBe expected(("b = _tmp_1.b", AlwaysEdge))
       succOf("b = _tmp_1.b") shouldBe expected(("_tmp_1", 4, AlwaysEdge))
-      succOf("_tmp_1", 4) shouldBe expected(("{id = {}, b}", AlwaysEdge))
-      succOf("{id = {}, b}") shouldBe expected(("id", AlwaysEdge))
+      succOf("_tmp_1", 4) shouldBe expected(("{id = {}, b} = {}", 1, AlwaysEdge))
+      succOf("{id = {}, b} = {}", 1) shouldBe expected(("id", AlwaysEdge))
 
     }
 
@@ -252,8 +252,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("1") shouldBe expected(("_tmp_0[1]", AlwaysEdge))
       succOf("_tmp_0[1]") shouldBe expected(("b = _tmp_0[1]", AlwaysEdge))
       succOf("b = _tmp_0[1]") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
-      succOf("_tmp_0", 3) shouldBe expected(("[a, b]", AlwaysEdge))
-      succOf("[a, b]") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("var [a, b] = x", AlwaysEdge))
+      succOf("var [a, b] = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for array destruction assignment without declaration" in {
@@ -275,8 +275,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("1") shouldBe expected(("_tmp_0[1]", AlwaysEdge))
       succOf("_tmp_0[1]") shouldBe expected(("b = _tmp_0[1]", AlwaysEdge))
       succOf("b = _tmp_0[1]") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
-      succOf("_tmp_0", 3) shouldBe expected(("[a, b]", AlwaysEdge))
-      succOf("[a, b]") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("[a, b] = x", AlwaysEdge))
+      succOf("[a, b] = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for array destruction assignment with defaults" in {
@@ -320,8 +320,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
         ("b = _tmp_0[1] === void 0 ? 2 : _tmp_0[1]", AlwaysEdge)
       )
       succOf("b = _tmp_0[1] === void 0 ? 2 : _tmp_0[1]") shouldBe expected(("_tmp_0", 5, AlwaysEdge))
-      succOf("_tmp_0", 5) shouldBe expected(("[a = 1, b = 2]", AlwaysEdge))
-      succOf("[a = 1, b = 2]") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 5) shouldBe expected(("var [a = 1, b = 2] = x", AlwaysEdge))
+      succOf("var [a = 1, b = 2] = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for array destruction assignment with ignores" in {
@@ -343,8 +343,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("2") shouldBe expected(("_tmp_0[2]", AlwaysEdge))
       succOf("_tmp_0[2]") shouldBe expected(("b = _tmp_0[2]", AlwaysEdge))
       succOf("b = _tmp_0[2]") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
-      succOf("_tmp_0", 3) shouldBe expected(("[a, , b]", AlwaysEdge))
-      succOf("[a, , b]") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("var [a, , b] = x", AlwaysEdge))
+      succOf("var [a, , b] = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for array destruction assignment with rest" in {
@@ -366,8 +366,8 @@ class MixedCfgCreationPassTest extends CfgTestFixture(() => new JsCfgTestCpg())
       succOf("1") shouldBe expected(("_tmp_0[1]", AlwaysEdge))
       succOf("_tmp_0[1]") shouldBe expected(("rest = _tmp_0[1]", AlwaysEdge))
       succOf("rest = _tmp_0[1]") shouldBe expected(("_tmp_0", 3, AlwaysEdge))
-      succOf("_tmp_0", 3) shouldBe expected(("[a, ...rest]", AlwaysEdge))
-      succOf("[a, ...rest]") shouldBe expected(("RET", AlwaysEdge))
+      succOf("_tmp_0", 3) shouldBe expected(("var [a, ...rest] = x", AlwaysEdge))
+      succOf("var [a, ...rest] = x") shouldBe expected(("RET", AlwaysEdge))
     }
 
     "be correct for array destruction assignment as parameter" in {