From 64ca4c7c3da1f13763518f0637db5abb5b7c7fab Mon Sep 17 00:00:00 2001 From: Markus Lottmann Date: Wed, 15 Feb 2023 16:26:05 +0100 Subject: [PATCH] Fix meta class call handler generation. (#2271) In cases were the "self" parameter is not explicitly given in a class __init__ function we did crash because we assumed that at least one positional parameter is always defined. This is now fixed. --- .../io/joern/pysrc2cpg/PythonAstVisitor.scala | 39 ++++++++++-------- .../joern/pysrc2cpg/cpg/ClassCpgTests.scala | 41 +++++++++++++++++++ 2 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/ClassCpgTests.scala 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 99279a48dabd..a74e0b68884e 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 @@ -595,6 +595,25 @@ class PythonAstVisitor( (convertedArgs, convertedKeywordArgs) } + /** This function strips the first positional parameter from initParameters, if present. + * @return + * Parameters without first positional parameter and adjusted line and column number information. + */ + private def stripFirstPositionalParameter(initParameters: ast.Arguments): (ast.Arguments, LineAndColumn) = { + if (initParameters.posonlyargs.nonEmpty) { + ( + initParameters.copy(posonlyargs = initParameters.posonlyargs.tail), + lineAndColOf(initParameters.posonlyargs.head) + ) + } else if (initParameters.args.nonEmpty) { + (initParameters.copy(args = initParameters.args.tail), lineAndColOf(initParameters.args.head)) + } else if (initParameters.vararg.nonEmpty) { + (initParameters, lineAndColOf(initParameters.vararg.get)) + } else { + (initParameters, lineAndColOf(initParameters.kw_arg.get)) + } + } + /** Creates the method which handles a call to the meta class object. This process is also known as creating a new * instance object, e.g. obj = MyClass(p1). The purpose of the generated function is to adapt between the special * cased instance creation call and a normal call to __new__ (for now ). The adaption is required to in @@ -614,15 +633,7 @@ class PythonAstVisitor( // We need to drop the "self" parameter either from the position only or normal parameters // because "self" is not passed through but rather created in __new__. - val (parametersWithoutSelf, lineAndColumn) = - if (initParameters.posonlyargs.nonEmpty) { - ( - initParameters.copy(posonlyargs = initParameters.posonlyargs.tail), - lineAndColOf(initParameters.posonlyargs.head) - ) - } else { - (initParameters.copy(args = initParameters.args.tail), lineAndColOf(initParameters.args.head)) - } + val (parametersWithoutSelf, lineAndColumn) = stripFirstPositionalParameter(initParameters) createMethod( methodName, @@ -671,15 +682,7 @@ class PythonAstVisitor( // We need to drop the "self" parameter either from the position only or normal parameters // because "self" is not passed through but rather created in __new__. - val (parametersWithoutSelf, lineAndColumn) = - if (initParameters.posonlyargs.nonEmpty) { - ( - initParameters.copy(posonlyargs = initParameters.posonlyargs.tail), - lineAndColOf(initParameters.posonlyargs.head) - ) - } else { - (initParameters.copy(args = initParameters.args.tail), lineAndColOf(initParameters.args.head)) - } + val (parametersWithoutSelf, lineAndColumn) = stripFirstPositionalParameter(initParameters) createMethod( newMethodName, diff --git a/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/ClassCpgTests.scala b/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/ClassCpgTests.scala new file mode 100644 index 000000000000..6d2001aa41bd --- /dev/null +++ b/joern-cli/frontends/pysrc2cpg/src/test/scala/io/joern/pysrc2cpg/cpg/ClassCpgTests.scala @@ -0,0 +1,41 @@ +package io.joern.pysrc2cpg.cpg + +import io.joern.pysrc2cpg.PySrc2CpgFixture +import io.shiftleft.semanticcpg.language._ + +class ClassCpgTests extends PySrc2CpgFixture(withOssDataflow = false) { + "class meta call handler" should { + "have no self parameter if self is explicit" in { + val cpg = code("""class Foo: + | def __init__(self, x): + | pass + |""".stripMargin) + + val handlerMethod = cpg.method.name("").head + handlerMethod.fullName shouldBe "Test0.py:.Foo." + handlerMethod.lineNumber shouldBe Some(2) + + handlerMethod.parameter.size shouldBe 1 + val xParameter = handlerMethod.parameter.head + xParameter.name shouldBe "x" + + } + + "have no self parameter if self is in varargs" in { + val cpg = code("""class Foo: + | def __init__(*x): + | pass + |""".stripMargin) + + val handlerMethod = cpg.method.name("").head + handlerMethod.fullName shouldBe "Test0.py:.Foo." + handlerMethod.lineNumber shouldBe Some(2) + + handlerMethod.parameter.size shouldBe 1 + val xParameter = handlerMethod.parameter.head + xParameter.name shouldBe "x" + + } + } + +}