From dfa2775111b2559934a206b81d15bc7ad692dc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Leuth=C3=A4user?= <1417198+max-leuthaeuser@users.noreply.github.com> Date: Wed, 20 Dec 2023 11:46:47 +0100 Subject: [PATCH] [swiftsrc2cpg] Added support for index access of list-like structures (#3981) Also: fixes string segment syntax handling. --- .../astcreation/AstForExprSyntaxCreator.scala | 24 ++++++++--- .../astcreation/AstForSwiftTokenCreator.scala | 2 +- .../AstForSyntaxCollectionCreator.scala | 7 +++- .../astcreation/AstForSyntaxCreator.scala | 2 +- .../astcreation/AstNodeBuilder.scala | 5 ++- .../passes/DependenciesPass.scala | 15 +++---- .../passes/ast/DeclarationTests.scala | 42 +++++++++++++++---- .../dependency/DependenciesPassTests.scala | 4 +- 8 files changed, 73 insertions(+), 28 deletions(-) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala index d33642616968..e16bf0eca4e0 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForExprSyntaxCreator.scala @@ -234,8 +234,16 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { case Some(otherBase) => astForNodeWithFunctionReference(otherBase) } - val memberNode = createFieldIdentifierNode(code(member), line(member), column(member)) - createFieldAccessCallAst(baseAst, memberNode, line(node), column(node)) + + member.baseName match { + case l @ integerLiteral(_) => + val memberNode = astForIntegerLiteralToken(l) + createIndexAccessCallAst(baseAst, memberNode, line(node), column(node)) + case other => + val memberNode = createFieldIdentifierNode(code(other), line(other), column(other)) + createFieldAccessCallAst(baseAst, memberNode, line(node), column(node)) + } + } private def astForMissingExprSyntax(node: MissingExprSyntax): Ast = notHandledYet(node) @@ -255,9 +263,15 @@ trait AstForExprSyntaxCreator(implicit withSchemaValidation: ValidationMode) { astForNode(node.segments) } - private def astForSubscriptCallExprSyntax(node: SubscriptCallExprSyntax): Ast = notHandledYet(node) - private def astForSuperExprSyntax(node: SuperExprSyntax): Ast = notHandledYet(node) - private def astForSwitchExprSyntax(node: SwitchExprSyntax): Ast = notHandledYet(node) + private def astForSubscriptCallExprSyntax(node: SubscriptCallExprSyntax): Ast = { + val baseAst = astForNodeWithFunctionReference(node.calledExpression) + val memberAst = astForNode(node.arguments) + val additionalArgsAst = astForNode(node.additionalTrailingClosures) + createIndexAccessCallAst(baseAst, memberAst, line(node), column(node), additionalArgsAst) + } + + private def astForSuperExprSyntax(node: SuperExprSyntax): Ast = notHandledYet(node) + private def astForSwitchExprSyntax(node: SwitchExprSyntax): Ast = notHandledYet(node) private def astForTernaryExprSyntax(node: TernaryExprSyntax): Ast = { val name = Operators.conditional diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSwiftTokenCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSwiftTokenCreator.scala index 7006aacbe80f..0d88b4f9b167 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSwiftTokenCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSwiftTokenCreator.scala @@ -31,7 +31,7 @@ trait AstForSwiftTokenCreator(implicit withSchemaValidation: ValidationMode) { t private def astForInfixQuestionMarkToken(node: infixQuestionMark): Ast = Ast() - private def astForIntegerLiteralToken(node: integerLiteral): Ast = { + protected def astForIntegerLiteralToken(node: integerLiteral): Ast = { Ast(literalNode(node, code(node), Option(Defines.Int))) } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala index 7ef94a700eec..c73ac665f30d 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCollectionCreator.scala @@ -81,8 +81,11 @@ trait AstForSyntaxCollectionCreator(implicit withSchemaValidation: ValidationMod } private def astForMemberBlockItemListSyntax(node: MemberBlockItemListSyntax): Ast = notHandledYet(node) - private def astForMultipleTrailingClosureElementListSyntax(node: MultipleTrailingClosureElementListSyntax): Ast = - notHandledYet(node) + + private def astForMultipleTrailingClosureElementListSyntax(node: MultipleTrailingClosureElementListSyntax): Ast = { + astForListSyntaxChildren(node, node.children) + } + private def astForObjCSelectorPieceListSyntax(node: ObjCSelectorPieceListSyntax): Ast = notHandledYet(node) private def astForPatternBindingListSyntax(node: PatternBindingListSyntax): Ast = notHandledYet(node) private def astForPlatformVersionItemListSyntax(node: PlatformVersionItemListSyntax): Ast = notHandledYet(node) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala index 6b810601e311..379e6108d3e6 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstForSyntaxCreator.scala @@ -224,7 +224,7 @@ trait AstForSyntaxCreator(implicit withSchemaValidation: ValidationMode) { this: notHandledYet(node) private def astForStringSegmentSyntax(node: StringSegmentSyntax): Ast = { - Ast(literalNode(node, code(node), Option(Defines.String))) + Ast(literalNode(node, s"\"${code(node)}\"", Option(Defines.String))) } private def astForSwitchCaseItemSyntax(node: SwitchCaseItemSyntax): Ast = notHandledYet(node) diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala index 0442116f1f53..1caa8bd5efdb 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/astcreation/AstNodeBuilder.scala @@ -41,7 +41,8 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC baseAst: Ast, partAst: Ast, line: Option[Integer], - column: Option[Integer] + column: Option[Integer], + additionalArgsAst: Ast = Ast() ): Ast = { val callNode = createCallNode( s"${codeOf(baseAst.nodes.head)}[${codeOf(partAst.nodes.head)}]", @@ -50,7 +51,7 @@ trait AstNodeBuilder(implicit withSchemaValidation: ValidationMode) { this: AstC line, column ) - val arguments = List(baseAst, partAst) + val arguments = List(baseAst, partAst, additionalArgsAst) callAst(callNode, arguments) } diff --git a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/DependenciesPass.scala b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/DependenciesPass.scala index db273a34cbd8..33262730ad88 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/DependenciesPass.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/main/scala/io/joern/swiftsrc2cpg/passes/DependenciesPass.scala @@ -1,5 +1,6 @@ package io.joern.swiftsrc2cpg.passes +import io.joern.x2cpg.X2Cpg import io.shiftleft.codepropertygraph.Cpg import io.shiftleft.codepropertygraph.generated.nodes.Call import io.shiftleft.codepropertygraph.generated.nodes.NewDependency @@ -21,24 +22,24 @@ class DependenciesPass(cpg: Cpg) extends CpgPass(cpg) { call.argument.l match case Nil => // Do nothing case _ :: (pathArg: Call) :: Nil if pathArg.argument(1).code == "path" => - val name = pathArg.argument(2).code + val name = X2Cpg.stripQuotes(pathArg.argument(2).code) val dep = NewDependency().name(name) diffGraph.addNode(dep) case _ :: (nameArg: Call) :: (pathArg: Call) :: Nil if nameArg.argument(1).code == "name" && pathArg.argument(1).code == "path" => - val name = nameArg.argument(2).code - val path = pathArg.argument(2).code + val name = X2Cpg.stripQuotes(nameArg.argument(2).code) + val path = X2Cpg.stripQuotes(pathArg.argument(2).code) val dep = NewDependency().name(name).dependencyGroupId(path) diffGraph.addNode(dep) case _ :: (urlArg: Call) :: (versionArg: Call) :: Nil if urlArg.argument(1).code == "url" && versionIds.contains(versionArg.argument(1).code) => - val name = urlArg.argument(2).code - val version = versionArg.argument(2).code + val name = X2Cpg.stripQuotes(urlArg.argument(2).code) + val version = X2Cpg.stripQuotes(versionArg.argument(2).code) val dep = NewDependency().name(name).version(version) diffGraph.addNode(dep) case _ :: (urlArg: Call) :: (versionRange: Call) :: Nil if urlArg.argument(1).code == "url" => - val name = urlArg.argument(2).code - val version = versionRange.code + val name = X2Cpg.stripQuotes(urlArg.argument(2).code) + val version = versionRange.code.replaceAll("[\"']", "") val dep = NewDependency().name(name).version(version) diffGraph.addNode(dep) case call => diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DeclarationTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DeclarationTests.scala index 3fd9c869bf74..d248bd06245e 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DeclarationTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/ast/DeclarationTests.scala @@ -388,11 +388,24 @@ class DeclarationTests extends AbstractPassTest { arrayCall.argument.isCall.code.l shouldBe List("1: \"One\",", "2: \"Two\",", "3: \"Three\"") } - "testAddDictionaryElements" ignore AstFixture(""" - |var capitalCity = ["Nepal": "Kathmandu", "England": "London"] - |capitalCity["Japan"] = "Tokyo" - |print(capitalCity["Japan"]) - |""".stripMargin) { cpg => ??? } + "testAddDictionaryElements" in AstFixture(""" + |var elements = ["A": "1", "B": "2"] + |elements["A"] = "3" + |print(elements["A"]) + |""".stripMargin) { cpg => + val List(elementsAccess1, elementsAccess2) = cpg.call(Operators.indexAccess).l + elementsAccess1.code shouldBe "elements[\"A\"]" + val List(arg11) = elementsAccess1.argument(1).start.isIdentifier.l + arg11.name shouldBe "elements" + val List(arg12) = elementsAccess1.argument(2).start.isLiteral.l + arg12.code shouldBe "\"A\"" + + elementsAccess2.code shouldBe "elements[\"A\"]" + val List(arg21) = elementsAccess2.argument(1).start.isIdentifier.l + arg21.name shouldBe "elements" + val List(arg22) = elementsAccess2.argument(2).start.isLiteral.l + arg22.code shouldBe "\"A\"" + } "testTupleDeclaration" in AstFixture("var product = (\"MacBook\", 1099.99)") { cpg => val List(method) = cpg.method.nameExact("").l @@ -405,14 +418,27 @@ class DeclarationTests extends AbstractPassTest { arrayCall.name shouldBe Operators.arrayInitializer arrayCall.code shouldBe "(\"MacBook\", 1099.99)" arrayCall.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH - arrayCall.argument.isLiteral.code.l shouldBe List("MacBook", "1099.99") + arrayCall.argument.isLiteral.code.l shouldBe List("\"MacBook\"", "1099.99") } - "testTupleAccess" ignore AstFixture(""" + "testTupleAccess" in AstFixture(""" |var product = ("MacBook", 1099.99) |print("Name:", product.0) |print("Price:", product.1) - |""".stripMargin) { cpg => ??? } + |""".stripMargin) { cpg => + val List(elementsAccess1, elementsAccess2) = cpg.call(Operators.indexAccess).l + elementsAccess1.code shouldBe "product[0]" + val List(arg11) = elementsAccess1.argument(1).start.isIdentifier.l + arg11.name shouldBe "product" + val List(arg12) = elementsAccess1.argument(2).start.isLiteral.l + arg12.code shouldBe "0" + + elementsAccess2.code shouldBe "product[1]" + val List(arg21) = elementsAccess2.argument(1).start.isIdentifier.l + arg21.name shouldBe "product" + val List(arg22) = elementsAccess2.argument(2).start.isLiteral.l + arg22.code shouldBe "1" + } "testInitAccessorsWithDefaultValues" ignore AstFixture(""" |struct Test { diff --git a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/dependency/DependenciesPassTests.scala b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/dependency/DependenciesPassTests.scala index 17b360ee08e5..0256d2db72db 100644 --- a/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/dependency/DependenciesPassTests.scala +++ b/joern-cli/frontends/swiftsrc2cpg/src/test/scala/io/joern/swiftsrc2cpg/passes/dependency/DependenciesPassTests.scala @@ -43,11 +43,11 @@ class DependenciesPassTests extends SwiftSrc2CpgSuite { depB.dependencyGroupId shouldBe None depC.name shouldBe "https://github.com/DepC" - depC.version shouldBe """"1.2.3"..<"1.2.6"""" + depC.version shouldBe "1.2.3..<1.2.6" depC.dependencyGroupId shouldBe None depD.name shouldBe "https://github.com/DepD" - depD.version shouldBe """"1.2.3"..."1.2.6"""" + depD.version shouldBe "1.2.3...1.2.6" depD.dependencyGroupId shouldBe None depE.name shouldBe "https://github.com/DepE"