diff --git a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala index 45142e62be54..d21354bb69ea 100644 --- a/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala +++ b/joern-cli/frontends/pysrc2cpg/src/main/scala/io/joern/pysrc2cpg/PythonAstVisitor.scala @@ -1,6 +1,6 @@ package io.joern.pysrc2cpg -import io.joern.pysrc2cpg.PythonAstVisitor.{builtinPrefix, metaClassSuffix} +import PythonAstVisitor.{builtinPrefix, logger, metaClassSuffix, noLineAndColumn} import io.joern.pysrc2cpg.memop.* import io.joern.pythonparser.ast import io.joern.x2cpg.{AstCreatorBase, ValidationMode} @@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory import overflowdb.BatchedUpdate.DiffGraphBuilder import scala.collection.mutable -import PythonAstVisitor.logger object MethodParameters { def empty(): MethodParameters = { @@ -1321,8 +1320,12 @@ class PythonAstVisitor( case node: ast.Name => convert(node) case node: ast.List => convert(node) case node: ast.Tuple => convert(node) - case node: ast.Slice => unhandled(node) - case node: ast.StringExpList => convert(node) + case node: ast.Slice => + // Our expectation is that ast.Slice only appears as part of ast.Subscript + // and thus we should never get here becauase convert(ast.Subscript) + // directly handles the case of a nested ast.Slice. + unhandled(node) + case node: ast.StringExpList => convert(node) } } @@ -1887,7 +1890,24 @@ class PythonAstVisitor( } def convert(subscript: ast.Subscript): NewNode = { - createIndexAccess(convert(subscript.value), convert(subscript.slice), lineAndColOf(subscript)) + subscript.slice match { + case slice: ast.Slice => + val value = convert(subscript.value) + val lower = slice.lower.map(convert).getOrElse(nodeBuilder.literalNode("None", None, noLineAndColumn)) + val upper = slice.upper.map(convert).getOrElse(nodeBuilder.literalNode("None", None, noLineAndColumn)) + val step = slice.step.map(convert).getOrElse(nodeBuilder.literalNode("None", None, noLineAndColumn)) + + val code = nodeToCode.getCode(subscript) + val callNode = + nodeBuilder.callNode(code, ".slice", DispatchTypes.STATIC_DISPATCH, lineAndColOf(slice)) + + val args = value :: lower :: upper :: step :: Nil + addAstChildrenAsArguments(callNode, 1, args) + + callNode + case _ => + createIndexAccess(convert(subscript.value), convert(subscript.slice), lineAndColOf(subscript)) + } } def convert(starred: ast.Starred): NewNode = { @@ -1952,7 +1972,19 @@ class PythonAstVisitor( callNode } - def convert(slice: ast.Slice): NewNode = ??? + def convert(slice: ast.Slice): NewNode = { + val args = mutable.ArrayBuffer.empty[NewNode] + slice.lower.foreach(expr => args.append(convert(expr))) + slice.upper.foreach(expr => args.append(convert(expr))) + slice.step.foreach(expr => args.append(convert(expr))) + + val code = nodeToCode.getCode(slice) + val callNode = nodeBuilder.callNode(code, ".slice", DispatchTypes.STATIC_DISPATCH, lineAndColOf(slice)) + + addAstChildrenAsArguments(callNode, 1, args) + + callNode + } def convert(stringExpList: ast.StringExpList): NewNode = { val stringNodes = stringExpList.elts.map(convert) @@ -2059,6 +2091,8 @@ object PythonAstVisitor { val typingPrefix = "typing." val metaClassSuffix = "" + val noLineAndColumn = LineAndColumn(-1, -1, -1, -1, -1, -1) + // This list contains all functions from https://docs.python.org/3/library/functions.html#built-in-funcs // for python version 3.9.5. // There is a corresponding list in policies which needs to be updated if this one is updated and vice versa. diff --git a/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/SliceCpgTests.scala b/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/SliceCpgTests.scala new file mode 100644 index 000000000000..ece135da41e0 --- /dev/null +++ b/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/SliceCpgTests.scala @@ -0,0 +1,54 @@ +package io.joern.pysrc2cpg.cpg + +import io.joern.pysrc2cpg.PySrc2CpgFixture +import io.shiftleft.semanticcpg.language.* + +class SliceCpgTests extends PySrc2CpgFixture() { + "slice" should { + "have correct AST case 1" in { + val cpg = code(""" + |x[1:2:1] + |""".stripMargin) + + val sliceOperator = cpg.call.name(".slice").head + sliceOperator.code shouldBe "x[1:2:1]" + sliceOperator.argument(1).code shouldBe "x" + sliceOperator.argument(2).code shouldBe "1" + sliceOperator.argument(3).code shouldBe "2" + sliceOperator.argument(4).code shouldBe "1" + } + + "have correct AST case 2" in { + val cpg = code(""" + |x[::] + |""".stripMargin) + + val sliceOperator = cpg.call.name(".slice").head + sliceOperator.code shouldBe "x[::]" + sliceOperator.argument(1).code shouldBe "x" + sliceOperator.argument(2).code shouldBe "None" + sliceOperator.argument(3).code shouldBe "None" + sliceOperator.argument(4).code shouldBe "None" + } + + "have correct AST case 3" in { + val cpg = code(""" + |x[::][1:2] + |""".stripMargin) + + val outerOperator = cpg.call.name(".slice").codeExact("x[::][1:2]").head + val innerOperator = outerOperator.argument.argumentIndex(1).isCall.head + + innerOperator.code shouldBe "x[::]" + innerOperator.argument(1).code shouldBe "x" + innerOperator.argument(2).code shouldBe "None" + innerOperator.argument(3).code shouldBe "None" + innerOperator.argument(4).code shouldBe "None" + + outerOperator.argument(2).code shouldBe "1" + outerOperator.argument(3).code shouldBe "2" + outerOperator.argument(4).code shouldBe "None" + } + } + +}