Skip to content
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

[kotlin2cpg] Named arguments for KtCallExpression and implicit this #5048

Merged
merged 1 commit into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading