Repository: joernio/joern (scala)

Issue:
[Bug] kotlin2cpg creates duplicate AST edge in some cases
To reproduce, add the following test to `querying/LambdaTests.scala`:
```scala
    "foo" in {
      val List(td) = cpg.typeDecl.fullName(".*lambda.*").l
      val astParent = td._astIn.l
      astParent.foreach(println)
      astParent.size shouldBe 1
    }
```

Output:
```
io.shiftleft.codepropertygraph.generated.nodes.Block[label=BLOCK; seq=0; id=25769803776]
io.shiftleft.codepropertygraph.generated.nodes.Block[label=BLOCK; seq=0; id=25769803776]

2 was not equal to 1
ScalaTestFailureLocation: io.joern.kotlin2cpg.querying.LambdaTests at (LambdaTests.scala:167)
Expected :1
Actual   :2
```

Context: in https://github.com/joernio/joern/pull/5585 we switch from `.onlyChecked` to `.loneElement`. The idea was always to ensure there's exactly _one_ element in the collection. However the old `onlyChecked` would only log a warning if there was more than one element. That's different in `loneElement`, it throws an exception, which uncovered that kotlin2cpg creates duplicate AST edges.

Pull request description:
Bindings are created for the lambda Single Abstract Method (SAM) interface as well its native implementation. This is implemented after the typedecl node is created and stored with all the other binding information in the lambdaBindingInfoQueue (i.e., stored twice). When constructing the actual CPG AST, these nodes where added twice.

Fixes: https://github.com/joernio/joern/issues/5586

Patch:
diff --git a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala
index d019e125e3a9..846e56aa71d5 100644
--- a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala
+++ b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala
@@ -1,34 +1,27 @@
 package io.joern.kotlin2cpg.ast
 
-import io.joern.kotlin2cpg.Constants
-import io.joern.kotlin2cpg.KtFileWithMeta
+import io.joern.kotlin2cpg.{Constants, KtFileWithMeta}
 import io.joern.kotlin2cpg.datastructures.Scope
-import io.joern.kotlin2cpg.types.NameRenderer
-import io.joern.kotlin2cpg.types.TypeConstants
-import io.joern.kotlin2cpg.types.TypeInfoProvider
-import io.joern.x2cpg.Ast
-import io.joern.x2cpg.AstCreatorBase
-import io.joern.x2cpg.Defines
-import io.joern.x2cpg.ValidationMode
+import io.joern.kotlin2cpg.types.{NameRenderer, TypeConstants, TypeInfoProvider}
+import io.joern.x2cpg.{Ast, AstCreatorBase, Defines, ValidationMode}
 import io.joern.x2cpg.datastructures.Global
 import io.joern.x2cpg.datastructures.Stack.*
 import io.joern.x2cpg.utils.IntervalKeyPool
 import io.shiftleft.codepropertygraph.generated.*
 import io.shiftleft.codepropertygraph.generated.nodes.*
-import io.shiftleft.codepropertygraph.generated.DiffGraphBuilder
 import io.shiftleft.semanticcpg.language.*
 import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
 import org.jetbrains.kotlin.com.intellij.psi.PsiElement
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
-import org.jetbrains.kotlin.descriptors.DescriptorVisibility
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.lexer.KtToken
-import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.descriptors.{
+  DeclarationDescriptor,
+  DescriptorVisibilities,
+  DescriptorVisibility,
+  FunctionDescriptor
+}
+import org.jetbrains.kotlin.lexer.{KtToken, KtTokens}
 import org.jetbrains.kotlin.psi.*
 import org.jetbrains.kotlin.resolve.BindingContext
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
+import org.slf4j.{Logger, LoggerFactory}
 
 import scala.annotation.tailrec
 import scala.collection.mutable
@@ -54,8 +47,7 @@ class AstCreator(
     with AstForStatementsCreator
     with AstForExpressionsCreator {
 
-  import AstCreator.BindingInfo
-  import AstCreator.ClosureBindingDef
+  import AstCreator.{BindingInfo, ClosureBindingDef}
 
   protected val closureBindingDefQueue: mutable.ArrayBuffer[ClosureBindingDef] = mutable.ArrayBuffer.empty
   protected val bindingInfoQueue: mutable.ArrayBuffer[BindingInfo]             = mutable.ArrayBuffer.empty
@@ -396,7 +388,7 @@ class AstCreator(
       lambdaBindingInfoQueue.flatMap(_.edgeMeta.collect { case (node: NewTypeDecl, _, _) => Ast(node) })
     methodAstParentStack.pop()
 
-    val allDeclarationAsts = declarationsAsts ++ lambdaAstQueue ++ lambdaTypeDecls
+    val allDeclarationAsts = declarationsAsts ++ lambdaAstQueue ++ lambdaTypeDecls.distinct
     val fakeTypeDeclAst =
       Ast(fakeGlobalTypeDecl)
         .withChild(


Test patch:
diff --git a/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/LambdaTests.scala b/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/LambdaTests.scala
index f154d9dbbead..48757c8fdf36 100644
--- a/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/LambdaTests.scala
+++ b/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/LambdaTests.scala
@@ -166,6 +166,8 @@ class LambdaTests extends KotlinCode2CpgFixture(withOssDataflow = false, withDef
       td.code shouldBe "LAMBDA_TYPE_DECL"
       td.inheritsFromTypeFullName shouldBe Seq("kotlin.Function1")
       Option(td.astParent).isDefined shouldBe true
+      val astParent = td._astIn.l
+      astParent.size shouldBe 1
 
       val List(bm) = cpg.typeDecl.fullName(".*lambda.*").boundMethod.dedup.l
       bm.fullName shouldBe s"mypkg.foo.${Defines.ClosurePrefix}0:void(java.lang.String)"