[kotlin2cpg] Add support for KtCallableReferenceExpression#5775
[kotlin2cpg] Add support for KtCallableReferenceExpression#5775SuperUserDone wants to merge 7 commits intomasterfrom
Conversation
...rontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/CallableReferenceTests.scala
Outdated
Show resolved
Hide resolved
...rontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/CallableReferenceTests.scala
Outdated
Show resolved
Hide resolved
...rontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/CallableReferenceTests.scala
Outdated
Show resolved
Hide resolved
...rontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/CallableReferenceTests.scala
Outdated
Show resolved
Hide resolved
8d8282f to
47108a6
Compare
47108a6 to
ee3e39e
Compare
|
The latest set of changes are major overhaul of the methodology of this PR. This now closely follows what the kotlin compiler does. For unbound references to a global function For bound references to a companion object This does not work around kotlin compiler bug https://youtrack.jetbrains.com/issue/KT-54316, so the behavior is identical to the Kotlin compiler in the documented case. This only affects the companion case for assigned references. This affects cases like this. This code won't compile because the Utils::validate ref has a broken signature of (Utils, Int) -> Boolean. class Utils {
companion object {
fun validate(x: Int): Boolean = x > 0
}
}
val ref: (Int) -> Boolean = Utils::validateThis case is fine tough and types are resolved correctly class Utils {
companion object {
fun validate(x: Int): Boolean = x > 0
}
}
fun bar(ref: (Int) -> Boolean) {}
bar(Utils::validate) |
johannescoetzee
left a comment
There was a problem hiding this comment.
I've only checked the tests so far, but have left a few comments about the higher-level design and some nitpicks. I'll probably only get to reviewing the implementation tomorrow.
| val forwardedArgs = processCall.argument.isIdentifier.l | ||
| forwardedArgs.size shouldBe 2 | ||
| forwardedArgs.map(_.typeFullName).toSet shouldBe Set("int", "java.lang.String") |
There was a problem hiding this comment.
With the way this is written, it's possible to miss non-identifier arguments or to have the correct arguments in the wrong order without the test catching it. It's a nitpick, but in my opinion it is better to test explicitly, so something like
processCall.argument.map(_.typeFullName).toList shouldBe List(<something>, "int", "java.lang.String")
or
inside(processCall.argument.l) { case List(<something>, intArg: Identifier, stringArg: Identifier) =>
intArg.typeFullName shouldBe "int"
...
}
It's more cumbersome to write and can be harder to read, but is also occasionally useful for catching weird bugs
There was a problem hiding this comment.
Please check that REF edges from the process arguments to the invoke parameters are being created correctly as well (in this method, but also wherever an Identifier node is added to the CPG). A lot of dataflow issues in the past were caused by missing REF edges (or REF edges referring to the wrong local/param) and testing it in the frontend AST tests is much faster than figuring it out through dataflow debugging
There was a problem hiding this comment.
Have adjusted some of the tests to use this pattern where I thought it made sense. In most of the tests also added checks for the ref edges to the identifier nodes. There was a bug that I caught through that and resolved.
...rontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/CallableReferenceTests.scala
Show resolved
Hide resolved
...rontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/CallableReferenceTests.scala
Outdated
Show resolved
Hide resolved
...rontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/CallableReferenceTests.scala
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| "create a constructor call for the synthetic type with receiver as parameter" in { | ||
| val ctorCalls = cpg.call.nameExact("<init>").methodFullName(".*Function2Impl.*<init>.*").l |
There was a problem hiding this comment.
| val ctorCalls = cpg.call.nameExact("<init>").methodFullName(".*Function2Impl.*<init>.*").l | |
| val ctorCalls = cpg.call.methodFullName(".*Function2Impl.*<init>.*").l |
should return the same, unless there's a type decl with <init> in the name. You could also use methodFullNameExact with the exact method full name to avoid any conflicts.
There was a problem hiding this comment.
This is perhaps a stylistic thing, but I also prefer to write these in a way that gives more information about where the call occurs, for example cpg.method("test").call.... How far to take this depends on the complexity of the code in question though and here just finding the <init> directly is probably fine
No description provided.