Skip to content

Commit ec56591

Browse files
[kotlin2cpg] Fix Incorrect Package-Level Global Variable Handling (#5839)
* [kotlin2cpg] Add tests for both cases in the issue * Removed Frontend Validation in test
1 parent dea4171 commit ec56591

File tree

3 files changed

+97
-8
lines changed

3 files changed

+97
-8
lines changed

joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import org.jetbrains.kotlin.it.unimi.dsi.fastutil.objects.s
3434
object AstCreator {
3535
case class AnonymousObjectContext(declaration: KtElement)
3636
case class BindingInfo(node: NewBinding, edgeMeta: Seq[(NewNode, NewNode, String)])
37-
case class ClosureBindingDef(node: NewClosureBinding, captureEdgeTo: NewMethodRef, refEdgeTo: NewNode)
37+
case class ClosureBindingDef(node: NewClosureBinding, captureEdgeTo: Option[NewMethodRef], refEdgeTo: NewNode)
3838

3939
case class ReceiverInfo(
4040
ctorParamAst: Ast,
@@ -259,7 +259,7 @@ class AstCreator(
259259

260260
closureBindingDefQueue.foreach { case ClosureBindingDef(node, captureEdgeTo, refEdgeTo) =>
261261
diffGraph.addNode(node)
262-
diffGraph.addEdge(captureEdgeTo, node, EdgeTypes.CAPTURE)
262+
captureEdgeTo.foreach(diffGraph.addEdge(_, node, EdgeTypes.CAPTURE))
263263
diffGraph.addEdge(node, refEdgeTo, EdgeTypes.REF)
264264
}
265265
}

joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstForFunctionsCreator.scala

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,41 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) exte
7878
val fullName = nameRenderer.combineFunctionFullName(descFullName, signature)
7979

8080
val _methodNode = methodNode(ktFn, ktFn.getName, fullName, signature, relativizedPath)
81-
scope.pushNewScope(_methodNode)
81+
val closureBindingEntriesForCaptured =
82+
if (ktFn.getName == "main") {
83+
scope
84+
.pushClosureScope(_methodNode)
85+
.collect { case node: NewLocal => NodeContext(node, node.name, node.typeFullName) }
86+
.map { capturedNodeContext =>
87+
val closureBindingId = s"${_methodNode.fullName}:${capturedNodeContext.name}"
88+
val closureBinding = closureBindingNode(closureBindingId, EvaluationStrategies.BY_REFERENCE)
89+
(closureBinding, capturedNodeContext)
90+
}
91+
} else {
92+
scope.pushNewScope(_methodNode)
93+
List.empty
94+
}
95+
val methodRefForCapture =
96+
if (closureBindingEntriesForCaptured.nonEmpty) {
97+
val methodTypeDeclFullName = fullName.split(":").head
98+
Some(methodRefNode(ktFn, ktFn.getName, fullName, methodTypeDeclFullName))
99+
} else {
100+
None
101+
}
102+
103+
val localsForCaptured = closureBindingEntriesForCaptured.map { case (closureBinding, capturedNodeContext) =>
104+
val node =
105+
localNode(
106+
ktFn,
107+
capturedNodeContext.name,
108+
capturedNodeContext.name,
109+
capturedNodeContext.typeFullName,
110+
closureBinding.closureBindingId
111+
)
112+
scope.addToScope(capturedNodeContext.name, node)
113+
node
114+
}
115+
82116
methodAstParentStack.push(_methodNode)
83117

84118
val isExtensionMethod = funcDesc.getExtensionReceiverParameter != null
@@ -128,7 +162,8 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) exte
128162
astForParameter(p, valueParamStartIndex + idx - 1)
129163
}
130164
val bodyAst = Option(ktFn.getBodyBlockExpression) match {
131-
case Some(bodyBlockExpression) => astForBlock(bodyBlockExpression, None, None)
165+
case Some(bodyBlockExpression) =>
166+
astForBlock(bodyBlockExpression, None, None, localsForCaptures = localsForCaptured)
132167
case None =>
133168
Option(ktFn.getBodyExpression)
134169
.map { expr =>
@@ -141,16 +176,21 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) exte
141176
val returnAst_ = returnAst(returnNode(expr, Constants.RetCode), Seq(lastStatementAst))
142177
(allStatementsButLast ++ Seq(returnAst_)).toList
143178
} else List()
144-
blockAst(bodyBlock, blockChildAsts)
179+
blockAst(bodyBlock, localsForCaptured.map(Ast(_)) ++ blockChildAsts)
145180
}
146181
.getOrElse {
147182
val bodyBlock = blockNode(ktFn, "<empty>", TypeConstants.Any)
148-
blockAst(bodyBlock, List[Ast]())
183+
blockAst(bodyBlock, localsForCaptured.map(Ast(_)))
149184
}
150185
}
151186
methodAstParentStack.pop()
152187
scope.popScope()
153188

189+
val closureBindingDefs = closureBindingEntriesForCaptured.map { case (closureBinding, node) =>
190+
ClosureBindingDef(closureBinding, methodRefForCapture, node.node)
191+
}
192+
closureBindingDefs.foreach(closureBindingDefQueue.prepend)
193+
154194
val explicitTypeName = Option(ktFn.getTypeReference).map(_.getText).getOrElse(TypeConstants.Any)
155195
val typeFullName = registerType(nameRenderer.typeFullName(funcDesc.getReturnType).getOrElse(explicitTypeName))
156196
val _methodReturnNode = methodReturnNode(ktFn, typeFullName)
@@ -183,6 +223,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) exte
183223
val annotationEntries = ktFn.getAnnotationEntries.asScala.map(astForAnnotationEntry).toSeq
184224
methodAst(_methodNode, thisParameterAsts ++ methodParametersAsts, bodyAst, _methodReturnNode, modifiers)
185225
.withChildren(annotationEntries)
226+
.withChildren(methodRefForCapture.map(Ast(_)).toSeq)
186227
}
187228

188229
private def astsForDestructuring(param: KtParameter, backingParamName: Option[String] = None): Seq[Ast] = {
@@ -485,7 +526,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) exte
485526

486527
scope.popScope()
487528
val closureBindingDefs = closureBindingEntriesForCaptured.collect { case (closureBinding, node) =>
488-
ClosureBindingDef(closureBinding, _methodRefNode, node.node)
529+
ClosureBindingDef(closureBinding, Some(_methodRefNode), node.node)
489530
}
490531
closureBindingDefs.foreach(closureBindingDefQueue.prepend)
491532
lambdaAstQueue.prepend(lambdaMethodAst)
@@ -656,7 +697,7 @@ trait AstForFunctionsCreator(implicit withSchemaValidation: ValidationMode) exte
656697

657698
scope.popScope()
658699
val closureBindingDefs = closureBindingEntriesForCaptured.collect { case (closureBinding, node) =>
659-
ClosureBindingDef(closureBinding, _methodRefNode, node.node)
700+
ClosureBindingDef(closureBinding, Some(_methodRefNode), node.node)
660701
}
661702
closureBindingDefs.foreach(closureBindingDefQueue.prepend)
662703
lambdaAstQueue.prepend(lambdaMethodAst)

joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/validation/IdentifierReferencesTests.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package io.joern.kotlin2cpg.validation
22

33
import io.joern.kotlin2cpg.testfixtures.KotlinCode2CpgFixture
44
import io.shiftleft.codepropertygraph.generated.Operators
5+
import io.shiftleft.codepropertygraph.generated.edges.Capture
56
import io.shiftleft.codepropertygraph.generated.nodes.{Identifier, Local, MethodParameterIn}
67
import io.shiftleft.semanticcpg.language.*
8+
import io.shiftleft.semanticcpg.validation.PostFrontendValidator
9+
import io.shiftleft.codepropertygraph.generated.nodes.Member
710

811
// TODO: also add test with refs inside TYPE_DECL
912

@@ -203,4 +206,49 @@ class IdentifierReferencesTests extends KotlinCode2CpgFixture(withOssDataflow =
203206
methodParam.name shouldBe "this"
204207
}
205208
}
209+
210+
"CPG for code with a package-level global referenced from main" should {
211+
lazy val cpg = code("""
212+
|package com.example
213+
|
214+
|val globalVar = 42
215+
|
216+
|fun main(): Unit {
217+
| print(globalVar)
218+
|}
219+
|""".stripMargin)
220+
"have a LOCAL node for the global in <global> method" in {
221+
val List(globalMethod) = cpg.method.nameExact("<global>").l
222+
val List(globalLocal) = globalMethod.local.nameExact("globalVar").l
223+
globalLocal.name shouldBe "globalVar"
224+
globalLocal.typeFullName shouldBe "int"
225+
globalLocal.closureBindingId shouldBe None
226+
}
227+
228+
"have a LOCAL with closure binding in main method" in {
229+
val List(mainLocal) = cpg.method.nameExact("main").local.nameExact("globalVar").l
230+
mainLocal.closureBindingId should not be None
231+
mainLocal.closureBindingId.get shouldBe "com.example.main:void():globalVar"
232+
}
233+
234+
"have a CLOSURE_BINDING connecting the two LOCALs" in {
235+
val List(globalMethod) = cpg.method.nameExact("<global>").l
236+
val List(globalLocal) = globalMethod.local.nameExact("globalVar").l
237+
val bindings = globalLocal.closureBinding.l
238+
bindings.size shouldBe 1
239+
bindings.head.closureBindingId should not be None
240+
bindings.head.closureBindingId.get shouldBe "com.example.main:void():globalVar"
241+
}
242+
243+
"have a CAPTURE edge from a METHOD_REF for main to the CLOSURE_BINDING" in {
244+
cpg.methodRef.methodFullNameExact("com.example.main:void()").size shouldBe 1
245+
cpg.methodRef.methodFullNameExact("com.example.main:void()").outE.collectAll[Capture].size shouldBe 1
246+
}
247+
248+
"have identifier in main referencing the LOCAL in main" in {
249+
val List(idInMain) = cpg.method.nameExact("main").ast.isIdentifier.nameExact("globalVar").l
250+
val List(localInMain) = cpg.method.nameExact("main").local.nameExact("globalVar").l
251+
idInMain.refsTo.l shouldBe List(localInMain)
252+
}
253+
}
206254
}

0 commit comments

Comments
 (0)