From 7f4b4c7f8d24822cfeff0187268ddbddb5408276 Mon Sep 17 00:00:00 2001 From: Claudiu-Vlad Ursache Date: Tue, 15 Aug 2023 14:48:20 +0200 Subject: [PATCH] kotlin2cpg: handle annotations for various exprs --- build.sbt | 2 +- .../io/joern/kotlin2cpg/ast/AstCreator.scala | 60 ++-- .../io/joern/kotlin2cpg/ast/KtPsiToAst.scala | 241 ++++++++----- .../querying/AnnotationsTests.scala | 336 ++++++++++++++++++ 4 files changed, 530 insertions(+), 109 deletions(-) diff --git a/build.sbt b/build.sbt index c1b562adb795..8369b6623ac1 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ name := "joern" ThisBuild / organization := "io.joern" ThisBuild / scalaVersion := "3.3.0" -val cpgVersion = "1.4.16" +val cpgVersion = "1.4.20" lazy val joerncli = Projects.joerncli lazy val querydb = Projects.querydb diff --git a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala index f43eed5a451f..751fdeb00da3 100644 --- a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala +++ b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/AstCreator.scala @@ -175,10 +175,11 @@ class AstCreator(fileWithMeta: KtFileWithMeta, xTypeInfoProvider: TypeInfoProvid argNameMaybe, typedExpr.getAnnotationEntries.asScala.toSeq ) - case typedExpr: KtArrayAccessExpression => Seq(astForArrayAccess(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtAnonymousInitializer => astsForExpression(typedExpr.getBody, argIdxMaybe) - case typedExpr: KtBinaryExpression => astsForBinaryExpr(typedExpr, argIdxMaybe, argNameMaybe, annotations) - case typedExpr: KtBlockExpression => astsForBlock(typedExpr, argIdxMaybe, argNameMaybe) + case typedExpr: KtArrayAccessExpression => + Seq(astForArrayAccess(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtAnonymousInitializer => astsForExpression(typedExpr.getBody, argIdxMaybe) + case typedExpr: KtBinaryExpression => astsForBinaryExpr(typedExpr, argIdxMaybe, argNameMaybe, annotations) + case typedExpr: KtBlockExpression => astsForBlock(typedExpr, argIdxMaybe, argNameMaybe) case typedExpr: KtBinaryExpressionWithTypeRHS => Seq(astForBinaryExprWithTypeRHS(typedExpr, argIdxMaybe, argNameMaybe, annotations)) case typedExpr: KtBreakExpression => Seq(astForBreak(typedExpr)) @@ -189,41 +190,48 @@ class AstCreator(fileWithMeta: KtFileWithMeta, xTypeInfoProvider: TypeInfoProvid Seq(astForClassLiteral(typedExpr, argIdxMaybe, argNameMaybe, annotations)) case typedExpr: KtSafeQualifiedExpression => Seq(astForQualifiedExpression(typedExpr, argIdxMaybe, argNameMaybe, annotations)) - case typedExpr: KtContinueExpression => Seq(astForContinue(typedExpr)) + case typedExpr: KtContinueExpression => Seq(astForContinue(typedExpr)) + // note: annotations are not currently (Kotlin 1.9.0) supported on destructuring declarations case typedExpr: KtDestructuringDeclaration => astsForDestructuringDeclaration(typedExpr) case typedExpr: KtDotQualifiedExpression => Seq(astForQualifiedExpression(typedExpr, argIdxMaybe, argNameMaybe, annotations)) - case typedExpr: KtDoWhileExpression => Seq(astForDoWhile(typedExpr)) - case typedExpr: KtForExpression => Seq(astForFor(typedExpr)) + case typedExpr: KtDoWhileExpression => Seq(astForDoWhile(typedExpr, annotations)) + case typedExpr: KtForExpression => Seq(astForFor(typedExpr, annotations)) case typedExpr: KtIfExpression => Seq(astForIf(typedExpr, argIdxMaybe, argNameMaybe, annotations)) - case typedExpr: KtIsExpression => Seq(astForIsExpression(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtLabeledExpression => astsForExpression(typedExpr.getBaseExpression, argIdxMaybe, argNameMaybe) - case typedExpr: KtLambdaExpression => Seq(astForLambda(typedExpr, argIdxMaybe, argNameMaybe)) + case typedExpr: KtIsExpression => Seq(astForIsExpression(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtLabeledExpression => + astsForExpression(typedExpr.getBaseExpression, argIdxMaybe, argNameMaybe, annotations) + case typedExpr: KtLambdaExpression => Seq(astForLambda(typedExpr, argIdxMaybe, argNameMaybe, annotations)) case typedExpr: KtNameReferenceExpression if typedExpr.getReferencedNameElementType == KtTokens.IDENTIFIER => - Seq(astForNameReference(typedExpr, argIdxMaybe, argNameMaybe)) + Seq(astForNameReference(typedExpr, argIdxMaybe, argNameMaybe, annotations)) // TODO: callable reference case _: KtNameReferenceExpression => Seq() case typedExpr: KtObjectLiteralExpression => Seq(astForObjectLiteralExpr(typedExpr, argIdxMaybe, argNameMaybe, annotations)) - case typedExpr: KtParenthesizedExpression => astsForExpression(typedExpr.getExpression, argIdxMaybe, argNameMaybe) - case typedExpr: KtPostfixExpression => Seq(astForPostfixExpression(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtPrefixExpression => Seq(astForPrefixExpression(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtProperty if typedExpr.isLocal => astsForProperty(typedExpr) - case typedExpr: KtReturnExpression => Seq(astForReturnExpression(typedExpr)) - case typedExpr: KtStringTemplateExpression => Seq(astForStringTemplate(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtSuperExpression => Seq(astForSuperExpression(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtThisExpression => Seq(astForThisExpression(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtThrowExpression => Seq(astForUnknown(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtTryExpression => Seq(astForTry(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtWhenExpression => Seq(astForWhen(typedExpr, argIdxMaybe, argNameMaybe)) - case typedExpr: KtWhileExpression => Seq(astForWhile(typedExpr)) + case typedExpr: KtParenthesizedExpression => + astsForExpression(typedExpr.getExpression, argIdxMaybe, argNameMaybe, annotations) + case typedExpr: KtPostfixExpression => + Seq(astForPostfixExpression(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtPrefixExpression => + Seq(astForPrefixExpression(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtProperty if typedExpr.isLocal => + astsForProperty(typedExpr, annotations ++ typedExpr.getAnnotationEntries.asScala.toSeq) + case typedExpr: KtReturnExpression => Seq(astForReturnExpression(typedExpr)) + case typedExpr: KtStringTemplateExpression => + Seq(astForStringTemplate(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtSuperExpression => Seq(astForSuperExpression(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtThisExpression => Seq(astForThisExpression(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtThrowExpression => Seq(astForUnknown(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtTryExpression => Seq(astForTry(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtWhenExpression => Seq(astForWhen(typedExpr, argIdxMaybe, argNameMaybe, annotations)) + case typedExpr: KtWhileExpression => Seq(astForWhile(typedExpr, annotations)) case typedExpr: KtNamedFunction if Option(typedExpr.getName).isEmpty => - Seq(astForAnonymousFunction(typedExpr, argIdxMaybe, argNameMaybe)) + Seq(astForAnonymousFunction(typedExpr, argIdxMaybe, argNameMaybe, annotations)) case typedExpr: KtNamedFunction => logger.debug( s"Creating empty AST node for unknown expression `${typedExpr.getClass}` with text `${typedExpr.getText}`." ) - Seq(astForUnknown(typedExpr, argIdxMaybe, argNameMaybe)) + Seq(astForUnknown(typedExpr, argIdxMaybe, argNameMaybe, annotations)) case null => logger.trace("Received null expression! Skipping...") Seq() @@ -232,7 +240,7 @@ class AstCreator(fileWithMeta: KtFileWithMeta, xTypeInfoProvider: TypeInfoProvid logger.debug( s"Creating empty AST node for unknown expression `${unknownExpr.getClass}` with text `${unknownExpr.getText}`." ) - Seq(astForUnknown(unknownExpr, argIdxMaybe, argNameMaybe)) + Seq(astForUnknown(unknownExpr, argIdxMaybe, argNameMaybe, annotations)) } } } diff --git a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/KtPsiToAst.scala b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/KtPsiToAst.scala index de20e4a0a3fc..ce31e317258c 100644 --- a/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/KtPsiToAst.scala +++ b/joern-cli/frontends/kotlin2cpg/src/main/scala/io/joern/kotlin2cpg/ast/KtPsiToAst.scala @@ -573,14 +573,18 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { returnAst(returnNode(expr, expr.getText), children.toList) } - def astForIsExpression(expr: KtIsExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForIsExpression( + expr: KtIsExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { registerType(typeInfoProvider.expressionType(expr, TypeConstants.any)) val args = astsForExpression(expr.getLeftHandSide, None) ++ Seq(astForTypeReference(expr.getTypeReference, None, argName)) val node = operatorCallNode(Operators.is, expr.getText, None, line(expr), column(expr)) callAst(withArgumentName(withArgumentIndex(node, argIdx), argName), args.toList) + .withChildren(annotations.map(astForAnnotationEntry)) } def astForBinaryExprWithTypeRHS( @@ -604,26 +608,34 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { Ast(withArgumentName(withArgumentIndex(node, argIdx), argName)) } - def astForSuperExpression(expr: KtSuperExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForSuperExpression( + expr: KtSuperExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val typeFullName = registerType(typeInfoProvider.expressionType(expr, TypeConstants.any)) val node = withArgumentName( withArgumentIndex(identifierNode(expr, expr.getText, expr.getText, typeFullName), argIdx), argName ) astWithRefEdgeMaybe(expr.getText, node) + .withChildren(annotations.map(astForAnnotationEntry)) } - def astForThisExpression(expr: KtThisExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForThisExpression( + expr: KtThisExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val typeFullName = registerType(typeInfoProvider.expressionType(expr, TypeConstants.any)) val node = withArgumentName( withArgumentIndex(identifierNode(expr, expr.getText, expr.getText, typeFullName), argIdx), argName ) astWithRefEdgeMaybe(expr.getText, node) + .withChildren(annotations.map(astForAnnotationEntry)) } def astForClassLiteral( @@ -647,9 +659,12 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { .withChildren(annotations.map(astForAnnotationEntry)) } - def astForAnonymousFunction(fn: KtNamedFunction, argIdxMaybe: Option[Int], argNameMaybe: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForAnonymousFunction( + fn: KtNamedFunction, + argIdxMaybe: Option[Int], + argNameMaybe: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val (fullName, signature) = typeInfoProvider.fullNameWithSignatureAsLambda(fn, lambdaKeyPool) val lambdaMethodNode = methodNode(fn, Constants.lambdaName, fullName, signature, relativizedPath) @@ -731,11 +746,15 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { lambdaBindingInfoQueue.prepend(bindingInfo) lambdaAstQueue.prepend(lambdaMethodAst) Ast(_methodRefNode) + .withChildren(annotations.map(astForAnnotationEntry)) } - def astForLambda(expr: KtLambdaExpression, argIdxMaybe: Option[Int], argNameMaybe: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForLambda( + expr: KtLambdaExpression, + argIdxMaybe: Option[Int], + argNameMaybe: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val (fullName, signature) = typeInfoProvider.fullNameWithSignature(expr, lambdaKeyPool) val lambdaMethodNode = methodNode(expr, Constants.lambdaName, fullName, signature, relativizedPath) @@ -848,11 +867,15 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { lambdaBindingInfoQueue.prepend(bindingInfo) lambdaAstQueue.prepend(lambdaMethodAst) Ast(_methodRefNode) + .withChildren(annotations.map(astForAnnotationEntry)) } - def astForArrayAccess(expression: KtArrayAccessExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForArrayAccess( + expression: KtArrayAccessExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val arrayExpr = expression.getArrayExpression val typeFullName = registerType(typeInfoProvider.expressionType(expression, TypeConstants.any)) val identifier = identifierNode(arrayExpr, arrayExpr.getText, arrayExpr.getText, typeFullName) @@ -869,11 +892,15 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { column(expression) ) callAst(withArgumentName(withArgumentIndex(callNode, argIdx), argName), List(identifierAst) ++ astsForIndexExpr) + .withChildren(annotations.map(astForAnnotationEntry)) } - def astForPostfixExpression(expr: KtPostfixExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForPostfixExpression( + expr: KtPostfixExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val operatorType = ktTokenToOperator(forPostfixExpr = true).applyOrElse( KtPsiUtil.getOperationToken(expr), { (token: KtToken) => @@ -886,11 +913,15 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { .filterNot(_.root == null) val node = operatorCallNode(operatorType, expr.getText, Option(typeFullName), line(expr), column(expr)) callAst(withArgumentName(withArgumentIndex(node, argIdx), argName), args) + .withChildren(annotations.map(astForAnnotationEntry)) } - def astForPrefixExpression(expr: KtPrefixExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForPrefixExpression( + expr: KtPrefixExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val operatorType = ktTokenToOperator(forPostfixExpr = false).applyOrElse( KtPsiUtil.getOperationToken(expr), { (token: KtToken) => @@ -903,6 +934,7 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { .filterNot(_.root == null) val node = operatorCallNode(operatorType, expr.getText, Option(typeFullName), line(expr), column(expr)) callAst(withArgumentName(withArgumentIndex(node, argIdx), argName), args) + .withChildren(annotations.map(astForAnnotationEntry)) } private def astsForDestructuringDeclarationWithRHS( @@ -1092,34 +1124,46 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { else astsForDestructuringDeclarationWithVarRHS(expr) } - def astForUnknown(expr: KtExpression, argIdx: Option[Int], argNameMaybe: Option[String]): Ast = { + def astForUnknown( + expr: KtExpression, + argIdx: Option[Int], + argNameMaybe: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val node = unknownNode(expr, Option(expr).map(_.getText).getOrElse(Constants.codePropUndefinedValue)) Ast(withArgumentIndex(node, argIdx).argumentName(argNameMaybe)) + .withChildren(annotations.map(astForAnnotationEntry)) } - def astForStringTemplate(expr: KtStringTemplateExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForStringTemplate( + expr: KtStringTemplateExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val typeFullName = registerType(typeInfoProvider.expressionType(expr, TypeConstants.any)) - if (expr.hasInterpolation) { - val args = expr.getEntries.filter(_.getExpression != null).zipWithIndex.map { case (entry, idx) => - val entryTypeFullName = registerType(typeInfoProvider.expressionType(entry.getExpression, TypeConstants.any)) - val valueCallNode = operatorCallNode( - Operators.formattedValue, - entry.getExpression.getText, - Option(entryTypeFullName), - line(entry.getExpression), - column(entry.getExpression) - ) - val valueArgs = astsForExpression(entry.getExpression, Some(idx + 1)) - callAst(valueCallNode, valueArgs.toList) + val outAst = + if (expr.hasInterpolation) { + val args = expr.getEntries.filter(_.getExpression != null).zipWithIndex.map { case (entry, idx) => + val entryTypeFullName = registerType(typeInfoProvider.expressionType(entry.getExpression, TypeConstants.any)) + val valueCallNode = operatorCallNode( + Operators.formattedValue, + entry.getExpression.getText, + Option(entryTypeFullName), + line(entry.getExpression), + column(entry.getExpression) + ) + val valueArgs = astsForExpression(entry.getExpression, Some(idx + 1)) + callAst(valueCallNode, valueArgs.toList) + } + val node = + operatorCallNode(Operators.formatString, expr.getText, Option(typeFullName), line(expr), column(expr)) + callAst(withArgumentName(withArgumentIndex(node, argIdx), argName), args.toIndexedSeq.toList) + } else { + val node = literalNode(expr, expr.getText, typeFullName) + Ast(withArgumentName(withArgumentIndex(node, argIdx), argName)) } - val node = operatorCallNode(Operators.formatString, expr.getText, Option(typeFullName), line(expr), column(expr)) - callAst(withArgumentName(withArgumentIndex(node, argIdx), argName), args.toIndexedSeq.toList) - } else { - val node = literalNode(expr, expr.getText, typeFullName) - Ast(withArgumentName(withArgumentIndex(node, argIdx), argName)) - } + outAst.withChildren(annotations.map(astForAnnotationEntry)) } private def astForQualifiedExpressionFieldAccess( @@ -1441,9 +1485,12 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { controlStructureAst(node, None, tryAstOption :: (clauseAsts ++ finallyAsts).toList) } - private def astForTryAsExpression(expr: KtTryExpression, argIdx: Option[Int], argNameMaybe: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + private def astForTryAsExpression( + expr: KtTryExpression, + argIdx: Option[Int], + argNameMaybe: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val typeFullName = registerType( // TODO: remove the `last` typeInfoProvider.expressionType(expr.getTryBlock.getStatements.asScala.last, TypeConstants.any) @@ -1457,17 +1504,23 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { .argumentName(argNameMaybe) callAst(withArgumentIndex(node, argIdx), List(tryBlockAst) ++ clauseAsts) + .withChildren(annotations.map(astForAnnotationEntry)) } // TODO: handle parameters passed to the clauses - def astForTry(expr: KtTryExpression, argIdx: Option[Int], argNameMaybe: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForTry( + expr: KtTryExpression, + argIdx: Option[Int], + argNameMaybe: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { if (KtPsiUtil.isStatement(expr)) astForTryAsStatement(expr) - else astForTryAsExpression(expr, argIdx, argNameMaybe) + else astForTryAsExpression(expr, argIdx, argNameMaybe, annotations) } - def astForWhile(expr: KtWhileExpression)(implicit typeInfoProvider: TypeInfoProvider): Ast = { + def astForWhile(expr: KtWhileExpression, annotations: Seq[KtAnnotationEntry] = Seq())(implicit + typeInfoProvider: TypeInfoProvider + ): Ast = { val conditionAst = astsForExpression(expr.getCondition, None).headOption val stmtAsts = astsForExpression(expr.getBody, None) val code = Option(expr.getText) @@ -1475,9 +1528,12 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { val columnNumber = column(expr) whileAst(conditionAst, stmtAsts, code, lineNumber, columnNumber) + .withChildren(annotations.map(astForAnnotationEntry)) } - def astForDoWhile(expr: KtDoWhileExpression)(implicit typeInfoProvider: TypeInfoProvider): Ast = { + def astForDoWhile(expr: KtDoWhileExpression, annotations: Seq[KtAnnotationEntry] = Seq())(implicit + typeInfoProvider: TypeInfoProvider + ): Ast = { val conditionAst = astsForExpression(expr.getCondition, None).headOption val stmtAsts = astsForExpression(expr.getBody, None) val code = Option(expr.getText) @@ -1485,6 +1541,7 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { val columnNumber = column(expr) doWhileAst(conditionAst, stmtAsts, code, lineNumber, columnNumber) + .withChildren(annotations.map(astForAnnotationEntry)) } // e.g. lowering: @@ -1709,9 +1766,13 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { ) } - def astForFor(expr: KtForExpression)(implicit typeInfoProvider: TypeInfoProvider): Ast = { - if (expr.getDestructuringDeclaration != null) astForForWithDestructuringLHS(expr) - else astForForWithSimpleVarLHS(expr) + def astForFor(expr: KtForExpression, annotations: Seq[KtAnnotationEntry] = Seq())(implicit + typeInfoProvider: TypeInfoProvider + ): Ast = { + val outAst = + if (expr.getDestructuringDeclaration != null) astForForWithDestructuringLHS(expr) + else astForForWithSimpleVarLHS(expr) + outAst.withChildren(annotations.map(astForAnnotationEntry)) } def astForWhenAsStatement(expr: KtWhenExpression, argIdx: Option[Int])(implicit @@ -1771,13 +1832,18 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { callAst(callNode, List(subjectBlockAst) ++ argAsts) } - def astForWhen(expr: KtWhenExpression, argIdx: Option[Int], argNameMaybe: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { - typeInfoProvider.usedAsExpression(expr) match { - case Some(true) => astForWhenAsExpression(expr, argIdx, argNameMaybe) - case _ => astForWhenAsStatement(expr, argIdx) - } + def astForWhen( + expr: KtWhenExpression, + argIdx: Option[Int], + argNameMaybe: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { + val outAst = + typeInfoProvider.usedAsExpression(expr) match { + case Some(true) => astForWhenAsExpression(expr, argIdx, argNameMaybe) + case _ => astForWhenAsStatement(expr, argIdx) + } + outAst.withChildren(annotations.map(astForAnnotationEntry)) } private def astsForWhenEntry(entry: KtWhenEntry, argIdx: Int)(implicit @@ -1800,17 +1866,20 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { annotations: Seq[KtAnnotationEntry] = Seq() )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val isChildOfControlStructureBody = expr.getParent.isInstanceOf[KtContainerNodeForControlStructureBody] - if (KtPsiUtil.isStatement(expr) && !isChildOfControlStructureBody) astForIfAsControlStructure(expr) + if (KtPsiUtil.isStatement(expr) && !isChildOfControlStructureBody) astForIfAsControlStructure(expr, annotations) else astForIfAsExpression(expr, argIdx, argNameMaybe, annotations) } - def astForIfAsControlStructure(expr: KtIfExpression)(implicit typeInfoProvider: TypeInfoProvider): Ast = { + def astForIfAsControlStructure(expr: KtIfExpression, annotations: Seq[KtAnnotationEntry] = Seq())(implicit + typeInfoProvider: TypeInfoProvider + ): Ast = { val conditionAst = astsForExpression(expr.getCondition, None).headOption val thenAsts = astsForExpression(expr.getThen, None) val elseAsts = astsForExpression(expr.getElse, None) val node = controlStructureNode(expr, ControlStructureTypes.IF, expr.getText) controlStructureAst(node, conditionAst, List(thenAsts ++ elseAsts).flatten) + .withChildren(annotations.map(astForAnnotationEntry)) } def astForIfAsExpression( @@ -1955,7 +2024,9 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { .withChildren(annotations.map(astForAnnotationEntry)) } - def astsForProperty(expr: KtProperty)(implicit typeInfoProvider: TypeInfoProvider): Seq[Ast] = { + def astsForProperty(expr: KtProperty, annotations: Seq[KtAnnotationEntry] = Seq())(implicit + typeInfoProvider: TypeInfoProvider + ): Seq[Ast] = { val explicitTypeName = Option(expr.getTypeReference).map(_.getText).getOrElse(TypeConstants.any) val elem = expr.getIdentifyingElement @@ -2016,7 +2087,9 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { astsForExpression(arg.getArgumentExpression, Option(idx), argNameOpt) }.flatten - val initAst = callAst(initCallNode, argAsts, Option(initReceiverAst)) + val initAst = + callAst(initCallNode, argAsts, Option(initReceiverAst)) + .withChildren(annotations.map(astForAnnotationEntry)) Seq(localAst, assignmentCallAst, initAst) } else if (hasRHSObjectLiteral) { val typedExpr = expr.getDelegateExpressionOrInitializer.asInstanceOf[KtObjectLiteralExpression] @@ -2059,9 +2132,10 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { val initReceiverNode = identifierNode(expr, identifier.name, identifier.name, identifier.typeFullName) val initReceiverAst = Ast(initReceiverNode).withRefEdge(initReceiverNode, node) - val initAst = callAst(initCallNode, Seq(), Option(initReceiverAst)) + val initAst = + callAst(initCallNode, Seq(), Option(initReceiverAst)) + .withChildren(annotations.map(astForAnnotationEntry)) Seq(typeDeclAst, localAst, assignmentCallAst, initAst) - } else { val typeFullName = registerType(typeInfoProvider.propertyType(expr, explicitTypeName)) val node = localNode(expr, expr.getName, expr.getName, typeFullName) @@ -2072,27 +2146,30 @@ trait KtPsiToAst(implicit withSchemaValidation: ValidationMode) { val identifier = identifierNode(elem, elem.getText, elem.getText, typeFullName) val identifierAst = astWithRefEdgeMaybe(identifier.name, identifier) val assignmentNode = operatorCallNode(Operators.assignment, expr.getText, None, line(expr), column(expr)) - val call = callAst(assignmentNode, List(identifierAst) ++ rhsAsts) - + val call = + callAst(assignmentNode, List(identifierAst) ++ rhsAsts) + .withChildren(annotations.map(astForAnnotationEntry)) Seq(localAst, call) } } - def astForNameReference(expr: KtNameReferenceExpression, argIdx: Option[Int], argName: Option[String])(implicit - typeInfoProvider: TypeInfoProvider - ): Ast = { + def astForNameReference( + expr: KtNameReferenceExpression, + argIdx: Option[Int], + argName: Option[String], + annotations: Seq[KtAnnotationEntry] = Seq() + )(implicit typeInfoProvider: TypeInfoProvider): Ast = { val isReferencingMember = scope.lookupVariable(expr.getIdentifier.getText) match { case Some(_: NewMember) => true case _ => false } - if (typeInfoProvider.isReferenceToClass(expr)) astForNameReferenceToType(expr, argIdx) - else if (isReferencingMember) { - astForNameReferenceToMember(expr, argIdx) - } else { - astForNonSpecialNameReference(expr, argIdx, argName) - } + val outAst = + if (typeInfoProvider.isReferenceToClass(expr)) astForNameReferenceToType(expr, argIdx) + else if (isReferencingMember) astForNameReferenceToMember(expr, argIdx) + else astForNonSpecialNameReference(expr, argIdx, argName) + outAst.withChildren(annotations.map(astForAnnotationEntry)) } private def astForNameReferenceToType(expr: KtNameReferenceExpression, argIdx: Option[Int])(implicit diff --git a/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/AnnotationsTests.scala b/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/AnnotationsTests.scala index ca62632c0ced..796a6cfe3d6b 100644 --- a/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/AnnotationsTests.scala +++ b/joern-cli/frontends/kotlin2cpg/src/test/scala/io/joern/kotlin2cpg/querying/AnnotationsTests.scala @@ -269,4 +269,340 @@ class AnnotationsTests extends KotlinCode2CpgFixture(withOssDataflow = false) { cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 } } + + "CPG for code with an annotation on an is-expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy 1 is Int + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a labeled expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy albel@ 1 + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a parenthesized expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy() (1) + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy()").size shouldBe 1 + } + } + + "CPG for code with an annotation on a prefix expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy ++1 + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a postfix expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy 1++ + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a string template" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy "something" + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a try expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy try { "a" } catch (e: Exception) { "b" } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a super expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |class A { + | fun fn1() { + | println(@Fancy super.toString()) + | } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a this expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |class A { + | fun fn1() { + | println(@Fancy this.toString()) + | } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a simple identifier expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | val i = 1 + | @Fancy i + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a do-while control structure" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | var i = 10 + | @Fancy + | do { + | println("tick") + | --i + | } while (i > 0) + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a while control structure" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | var i = 10 + | @Fancy + | while (i > 0) { + | println("tick") + | --i + | } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a for control structure" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy + | for (i in 1..10) { + | println("tick") + | } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a when control structure" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy + | when (Random(1).nextInt() % 2) { + | 1 -> println("this") + | else -> println("that") + | } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a when expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | val x = + | @Fancy + | when (Random(1).nextInt() % 2) { + | 1 -> "this" + | else -> "that" + | } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a local declaration" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy + | var i = 1 + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on an array access expression" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | val l = arrayOf(1, 2, 3) + | @Fancy + | l[0] + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on a lambda" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy + | { println("this") } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } + + "CPG for code with an annotation on an anonymous function" should { + val cpg = code(""" + |package mypkg + |@Target(AnnotationTarget.EXPRESSION) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + |fun fn1() { + | @Fancy + | fun() { println("this") } + |} + |""".stripMargin) + + "contain an ANNOTATION node" in { + cpg.all.collectAll[Annotation].codeExact("@Fancy").size shouldBe 1 + } + } }