Skip to content

Commit

Permalink
[kotlin2cpg] Named arguments for KtQualifiedExpression and implicit this
Browse files Browse the repository at this point in the history
Fixes: #5042
  • Loading branch information
max-leuthaeuser committed Nov 1, 2024
1 parent 3e05969 commit 988b118
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,7 @@ class AstCreator(
val astDerivedMethodFullName = expr.getSelectorExpression match {
case expression: KtCallExpression =>
val receiverPlaceholderType = Defines.UnresolvedNamespace
val shortName = expr.getSelectorExpression.getFirstChild.getText
val args = expression.getValueArguments
val shortName = expression.getFirstChild.getText
s"$receiverPlaceholderType.$shortName"
case _: KtNameReferenceExpression =>
Operators.fieldAccess
Expand All @@ -514,15 +513,22 @@ class AstCreator(
(astDerivedMethodFullName, astDerivedSignature)
}

protected def selectorExpressionArgAsts(expr: KtQualifiedExpression, startIndex: Int = 1)(implicit
protected def astsForKtCallExpressionArguments(callExpr: KtCallExpression, startIndex: Int = 1)(implicit
typeInfoProvider: TypeInfoProvider
): List[Ast] = {
val callExpr = expr.getSelectorExpression.asInstanceOf[KtCallExpression]
withIndex(callExpr.getValueArguments.asScala.toSeq) { case (arg, idx) =>
astsForExpression(arg.getArgumentExpression, Some(startIndex + idx - 1))
val argumentNameMaybe = Option(arg.getArgumentName).map(_.getText)
astsForExpression(arg.getArgumentExpression, Some(startIndex + idx - 1), argumentNameMaybe)
}.flatten.toList
}

protected def selectorExpressionArgAsts(expr: KtQualifiedExpression, startIndex: Int = 1)(implicit
typeInfoProvider: TypeInfoProvider
): List[Ast] = {
val callExpr = expr.getSelectorExpression.asInstanceOf[KtCallExpression]
astsForKtCallExpressionArguments(callExpr, startIndex)
}

protected def modifierTypeForVisibility(visibility: DescriptorVisibility): String = {
if (visibility.toString == DescriptorVisibilities.PUBLIC.toString)
ModifierTypes.PUBLIC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,11 +346,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
val initReceiverNode =
identifierNode(expr, tmpName, tmpName, localForTmpNode.typeFullName).argumentIndex(0)
val initReceiverAst = Ast(initReceiverNode).withRefEdge(initReceiverNode, localForTmpNode)

val argAsts = withIndex(call.getValueArguments.asScala.toSeq) { case (arg, idx) =>
astsForExpression(arg.getArgumentExpression, Some(idx))
}.flatten

val argAsts = astsForKtCallExpressionArguments(call)
val (fullName, signature) =
calleeFullnameAndSignature(
getCalleeExpr(rhsCall),
Expand Down Expand Up @@ -666,11 +662,7 @@ trait AstForDeclarationsCreator(implicit withSchemaValidation: ValidationMode) {
)
val initReceiverNode = identifierNode(expr, identifier.name, identifier.name, identifier.typeFullName)
val initReceiverAst = Ast(initReceiverNode).withRefEdge(initReceiverNode, local)

val argAsts = withIndex(callExpr.getValueArguments.asScala.toSeq) { case (arg, idx) =>
val argNameOpt = if (arg.isNamed) Option(arg.getArgumentName.getAsName.toString) else None
astsForExpression(arg.getArgumentExpression, Option(idx), argNameOpt)
}.flatten
val argAsts = astsForKtCallExpressionArguments(callExpr)

val initAst =
callAst(initCallNode, argAsts, Option(initReceiverAst))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import io.joern.x2cpg.utils.NodeBuilders
import io.shiftleft.codepropertygraph.generated.DispatchTypes
import io.shiftleft.codepropertygraph.generated.Operators
import io.shiftleft.codepropertygraph.generated.nodes.NewMethodRef
import org.jetbrains.kotlin.descriptors.{DescriptorVisibilities, FunctionDescriptor}
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.lexer.KtToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext

import scala.jdk.CollectionConverters.*

Expand Down Expand Up @@ -266,10 +266,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
val initReceiverNode = identifierNode(expr, identifier.name, identifier.name, identifier.typeFullName)
val initReceiverAst = Ast(initReceiverNode).withRefEdge(initReceiverNode, local)

val argAsts = withIndex(callExpr.getValueArguments.asScala.toSeq) { case (arg, idx) =>
val argNameOpt = if (arg.isNamed) Option(arg.getArgumentName.getAsName.toString) else None
astsForExpression(arg.getArgumentExpression, Option(idx), argNameOpt)
}.flatten
val argAsts = astsForKtCallExpressionArguments(callExpr)
val initAst = callAst(initCallNode, argAsts, Option(initReceiverAst))

val returningIdentifierNode = identifierNode(expr, identifier.name, identifier.name, identifier.typeFullName)
Expand Down Expand Up @@ -454,10 +451,7 @@ trait AstForExpressionsCreator(implicit withSchemaValidation: ValidationMode) {
argNameMaybe: Option[String],
annotations: Seq[KtAnnotationEntry] = Seq()
)(implicit typeInfoProvider: TypeInfoProvider): Seq[Ast] = {
val argAsts = withIndex(expr.getValueArguments.asScala.toSeq) { case (arg, idx) =>
val argNameOpt = if (arg.isNamed) Option(arg.getArgumentName.getAsName.toString) else None
astsForExpression(arg.getArgumentExpression, Option(idx), argNameOpt)
}.flatten
val argAsts = astsForKtCallExpressionArguments(expr)

// TODO: add tests for the empty `referencedName` here
val referencedName = Option(expr.getFirstChild)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import io.shiftleft.codepropertygraph.generated.nodes.NewMember
import io.shiftleft.codepropertygraph.generated.nodes.NewMethodParameterIn
import io.shiftleft.semanticcpg.language.*
import io.shiftleft.semanticcpg.language.types.structure.NamespaceTraversal
import org.jetbrains.kotlin.descriptors.{ClassifierDescriptor, PropertyDescriptor, ValueDescriptor}
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueDescriptor
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtConstantExpression
Expand Down Expand Up @@ -97,9 +98,10 @@ trait AstForPrimitivesCreator(implicit withSchemaValidation: ValidationMode) {
case Some(_: NewMember) => true
case _ => false
}
val isUsedAsImplicitThis = typeInfoProvider.usedAsImplicitThis(expr)
val outAst =
if (typeInfoProvider.isReferenceToClass(expr)) astForNameReferenceToType(expr, argIdx)
else if (isReferencingMember) astForNameReferenceToMember(expr, argIdx)
else if (isReferencingMember || isUsedAsImplicitThis) astForNameReferenceToMember(expr, argIdx)
else astForNonSpecialNameReference(expr, argIdx, argName)
outAst.withChildren(annotations.map(astForAnnotationEntry))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptorImpl
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.load.java.`lazy`.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaMethod
Expand Down Expand Up @@ -52,6 +53,20 @@ class DefaultTypeInfoProvider(val bindingContext: BindingContext) extends TypeIn
Option(mapForEntity.get(BindingContext.USED_AS_EXPRESSION.getKey)).map(_.booleanValue())
}

def usedAsImplicitThis(expr: KtNameReferenceExpression): Boolean = {
val mapForEntity = bindingsForEntity(bindingContext, expr)
val isCallExprWithTarget = Option(mapForEntity)
.map(_.getKeys)
.exists(ks =>
ks.contains(BindingContext.CALL.getKey)
&& ks.contains(BindingContext.USED_AS_EXPRESSION.getKey)
&& ks.contains(BindingContext.REFERENCE_TARGET.getKey)
)
isCallExprWithTarget && resolvedPropertyDescriptor(expr).exists { d =>
d.getDispatchReceiverParameter != null && d.getDispatchReceiverParameter.getName.asString() == "<this>"
}
}

def isStaticMethodCall(expr: KtQualifiedExpression): Boolean = {
resolvedCallDescriptor(expr)
.map(_.getSource)
Expand Down Expand Up @@ -87,6 +102,14 @@ class DefaultTypeInfoProvider(val bindingContext: BindingContext) extends TypeIn
}
}

private def resolvedPropertyDescriptor(expr: KtNameReferenceExpression): Option[PropertyDescriptor] = {
val descMaybe = for {
callForSubexpression <- Option(bindingContext.get(BindingContext.REFERENCE_TARGET, expr))
desc = callForSubexpression
} yield desc
descMaybe.collect { case desc: PropertyDescriptor => desc }
}

private def resolvedCallDescriptor(expr: KtExpression): Option[FunctionDescriptor] = {
val relevantSubexpression = subexpressionForResolvedCallInfo(expr)
val descMaybe = for {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,19 @@
package io.joern.kotlin2cpg.types

import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.psi.{
KtAnnotationEntry,
KtBinaryExpression,
KtCallExpression,
KtClassLiteralExpression,
KtClassOrObject,
KtDestructuringDeclarationEntry,
KtElement,
KtExpression,
KtFile,
KtLambdaExpression,
KtNameReferenceExpression,
KtNamedFunction,
KtParameter,
KtPrimaryConstructor,
KtProperty,
KtQualifiedExpression,
KtSecondaryConstructor,
KtTypeAlias,
KtTypeReference
}
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtQualifiedExpression

case class AnonymousObjectContext(declaration: KtElement)

trait TypeInfoProvider {

def usedAsExpression(expr: KtExpression): Option[Boolean]

def usedAsImplicitThis(expr: KtNameReferenceExpression): Boolean

def isStaticMethodCall(expr: KtQualifiedExpression): Boolean

def isReferenceToClass(expr: KtNameReferenceExpression): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,36 @@ class CallTests extends KotlinCode2CpgFixture(withOssDataflow = false) {

"CPG for code with named arguments in call on object" should {
val cpg = code("""
|package no.such.pkg
|fun outer() {
| Pair(1,2).copy(second = 3)
|}
|""".stripMargin)
|package no.such.pkg
|fun outer() {
| Pair(1,2).copy(second = 3)
|}
|""".stripMargin)

"contain a CALL node with arguments that have the argument name set" ignore {
"contain a CALL node with arguments that have the argument name set" in {
val List(c) = cpg.call.name("copy").l
c.argument(1).argumentName shouldBe Some("second")
}
}

"CPG for code with implicit this access on apply and run call" should {
val cpg = code("""
|package no.such.pkg
|
|fun outer() {
| Pair(1,2).apply { println(second) }
|}
|""".stripMargin)

"contain a CALL node with argument that is a this access" in {
val List(printCall) = cpg.call.name("println").l
val secondCall = printCall.argument(1).asInstanceOf[Call]
secondCall.methodFullName shouldBe Operators.fieldAccess
secondCall.code shouldBe "this.second"
secondCall.argument(1).asInstanceOf[Identifier].typeFullName shouldBe "kotlin.Pair"
}
}

"CPG for code with call with argument with type with upper bound" should {
val cpg = code("""
|package mypkg
Expand Down

0 comments on commit 988b118

Please sign in to comment.