-
Notifications
You must be signed in to change notification settings - Fork 381
[kotlin2cpg] Add support for KtCallableReferenceExpression #5775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,157 @@ | ||
| package io.joern.kotlin2cpg.querying | ||
|
|
||
| import io.joern.kotlin2cpg.testfixtures.KotlinCode2CpgFixture | ||
| import io.shiftleft.codepropertygraph.generated.nodes.{Identifier, MethodRef} | ||
| import io.shiftleft.semanticcpg.language.* | ||
|
|
||
| class CallableReferenceTests extends KotlinCode2CpgFixture(withOssDataflow = false) { | ||
|
|
||
| "CPG for code with simple callback usage" should { | ||
| "resolved callable references as call argument should be handled correctly" in { | ||
| val cpg = code(""" | ||
| |package com.test | ||
| | | ||
| |class Bar { | ||
| | fun bar(x: Int) {} | ||
| |} | ||
| | | ||
| |class Foo { | ||
| | fun doNothing(c: (Int) -> Unit) {} | ||
| | | ||
| | fun foo() { | ||
| | doNothing(Bar::bar) | ||
| | } | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| inside(cpg.call.name("doNothing").argument.l) { case List(thisArg: Identifier, methodRef: MethodRef) => | ||
| thisArg.name shouldBe "this" | ||
|
|
||
| methodRef.methodFullName shouldBe "com.test.Bar.bar:void(int)" | ||
| methodRef.typeFullName shouldBe "com.test.Bar" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is wrong. You seem to have looked at Javasrc2cpg, but that is/was sadly not a good example and Johannes is fixing it there as we speak. You basically have to create a new synthetic TYPE_DECL and a corresponding TYPE which is then referenced here in typeFullName. This new TYPE_DECL then needs bindings that reflect the implemented interface. I suggest you and @johannescoetzee talk tomorrow so that he can tell you the details. |
||
| methodRef.code shouldBe "Bar::bar" | ||
| } | ||
| } | ||
|
|
||
| "callable references with unresolved signature should be handled correctly" in { | ||
| val cpg = code(""" | ||
| |fun isOdd(x: Int) = x % 2 != 0 | ||
| |package com.test | ||
| | | ||
| |class Foo { | ||
| | fun doNothing(c: (Int) -> Unit) {} | ||
| | | ||
| | fun foo() { | ||
| | doNothing(Bar::bar) | ||
| | } | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| // When Bar class doesn't exist, the callable reference should still be created | ||
| val methodRefs = cpg.methodRef.code("Bar::bar").l | ||
| methodRefs.size shouldBe 1 | ||
| val methodRef = methodRefs.head | ||
|
|
||
| methodRef.methodFullName shouldBe "<unresolvedNamespace>.bar:<unresolvedSignature>" | ||
| methodRef.typeFullName shouldBe "ANY" | ||
| methodRef.code shouldBe "Bar::bar" | ||
| } | ||
|
|
||
| "unresolved callable references should be handled correctly" in { | ||
| val cpg = code(""" | ||
| |fun main() { | ||
| | someFunction(::unknownFunction) | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| val methodRefs = cpg.methodRef.code("::unknownFunction").l | ||
| methodRefs.size shouldBe 1 | ||
| val methodRef = methodRefs.head | ||
|
|
||
| methodRef.methodFullName shouldBe "<unresolvedNamespace>.unknownFunction:<unresolvedSignature>" | ||
| methodRef.typeFullName shouldBe "ANY" | ||
| methodRef.code shouldBe "::unknownFunction" | ||
| } | ||
|
|
||
| "resolved instance method refs should be handled correctly" in { | ||
| val cpg = code(""" | ||
| |package com.test | ||
| | | ||
| |fun firstOdd(x: Int): Int { | ||
| | val numbers = listOf(1, 2, x) | ||
| | val y = numbers.filter(::isOdd)[0] | ||
| | return y | ||
| |class Foo { | ||
| | fun doNothing(c: (Int) -> Unit) {} | ||
| | | ||
| | fun func(x: Int) {} | ||
| | | ||
| | fun foo() { | ||
| | val f = Foo() | ||
| | doNothing(f::func) | ||
| | } | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| inside(cpg.call.name("doNothing").argument.l) { case List(thisArg: Identifier, methodRef: MethodRef) => | ||
| thisArg.name shouldBe "this" | ||
|
|
||
| methodRef.methodFullName shouldBe "com.test.Foo.func:void(int)" | ||
| methodRef.typeFullName shouldBe "com.test.Foo" | ||
| methodRef.code shouldBe "f::func" | ||
| } | ||
| } | ||
|
|
||
| "instance method refs with 'this' receiver should be handled correctly" in { | ||
| val cpg = code(""" | ||
| |package com.test | ||
| | | ||
| |class Foo { | ||
| | fun doNothing(c: (Int) -> Unit) {} | ||
| | | ||
| | fun func(x: Int) {} | ||
| | | ||
| |fun main(args : Array<String>) { | ||
| | println(firstOdd(3)) | ||
| | fun foo() { | ||
| | doNothing(this::func) | ||
| | } | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| "should have a non-0 number of CALL nodes" in { | ||
| cpg.call.size should not be 0 | ||
| inside(cpg.call.name("doNothing").argument.l) { case List(thisArg: Identifier, methodRef: MethodRef) => | ||
| thisArg.name shouldBe "this" | ||
|
|
||
| methodRef.methodFullName shouldBe "com.test.Foo.func:void(int)" | ||
| methodRef.typeFullName shouldBe "com.test.Foo" | ||
| methodRef.code shouldBe "this::func" | ||
| } | ||
| } | ||
|
|
||
| // TODO: add the rest of the test cases | ||
| "callable references with collection receiver should be handled correctly" in { | ||
| val cpg = code(""" | ||
| |fun main() { | ||
| | val numbers = listOf(1, 2, 3) | ||
| | val reference = numbers::forEach | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| val methodRefs = cpg.methodRef.code("numbers::forEach").l | ||
| methodRefs.size shouldBe 1 | ||
| val methodRef = methodRefs.head | ||
|
|
||
| // The exact type will depend on Kotlin stdlib resolution | ||
| methodRef.methodFullName should include("forEach") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| methodRef.typeFullName should include("List") | ||
| methodRef.code shouldBe "numbers::forEach" | ||
| } | ||
|
|
||
| "unbound callable references to top-level functions should be handled correctly" in { | ||
| val cpg = code(""" | ||
| |fun isOdd(x: Int) = x % 2 != 0 | ||
| | | ||
| |fun main() { | ||
| | val numbers = listOf(1, 2, 3) | ||
| | numbers.filter(::isOdd) | ||
| |} | ||
| |""".stripMargin) | ||
|
|
||
| inside(cpg.call.name("filter").argument.l) { case List(receiver, methodRef: MethodRef) => | ||
| methodRef.methodFullName shouldBe "<unresolvedNamespace>.isOdd:boolean(int)" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does |
||
| methodRef.typeFullName shouldBe "ANY" | ||
| methodRef.code shouldBe "::isOdd" | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing for
thisargument does not really belong to the thing we want to check here. Please remove it.